5

I have a table that have a row of dynamic textbox. Example below:

enter image description here

I add the row in the table by clicking the [+] Add New Target a it below screen will appear:

enter image description here

I want to add validation class to all text box inside the table. So when the user click the save button, it will check all the text box.

I try to use this jquery to this:

 $('#tbTargetDetails tr').each(function () {
            $(this).find('td input:text').each(function (i,a) {
                // get each of the textbox and add validation class to it
            });
        });

I'm using MVC 5, jquery-1.10.2.js, jquery-1.10.2.min.js, jquery.validate* & Site.css that have class input.input-validation-error

In my models:

 public class ClsTargetInfo
    {
        public string ItemNumber_Target { get; set; }
        [Required]
        public string TargetColor_U { get; set; }
        [Required]
        public string TargetColor_V { get; set; }
        [Required]
        public string D90Target_U { get; set; }
        [Required]
        public string D90Target_V { get; set; }
        [Required]
        public string D10Target_U { get; set; }
        [Required]
        public string D10Target_V { get; set; }
        [Required]
        public string Thickness { get; set; }
        [Required]
        public string FilmWidth { get; set; }
        [Required]
        public string TargetDate { get; set; }
    }

I call above model inside another model:

public class abc
{
 public IList<ClsTargetInfo> TargetInfo { get; set; }
}

Below is the code when i adding the new row:

        $("#btnAddTarget").on("click", function (event) {
            AddTargetItem(jQuery('#txtTargetColorU').val(), jQuery('#txtD90TargetU').val(), jQuery('#txtD10TargetU').val(),
                           jQuery('#txtTargetColorV').val(), jQuery('#txtD90TargetV').val(), jQuery('#txtD10TargetV').val(),
                            jQuery('#txtThickness').val(), jQuery('#txtFilmWidth').val(), jQuery('#TargetDate').val());
});

function AddTargetItem(TargetColor_U, D90Target_U, D10Target_U, TargetColor_V, D90Target_V, D10Target_V, Thickness, FilmWidth, TargetDate) {
        var rowCount = $('#tbTargetDetails tr').length;
        //minus 1 row for header
        rowCount = rowCount - 2;

        var rowCountBil = rowCount + 1;

        var row = '<tr style="background-color:#ffffff;" id="tr_' + rowCount + '">';
        row += '<td style="font-weight:bold;padding-left:5px;padding-top:0px;padding-bottom:0px;padding-right:0px;vertical-align:middle">' + rowCountBil + '</td>';
        row += '<td style="padding-left:0px;padding-top:0px;padding-bottom:0px;padding-right:0px"><input class="form-control" id="TargetInfo_' + rowCount + '__TargetColor_U" name="TargetInfo[' + rowCount + '].TargetColor_U" type="text" value="' + TargetColor_U + '" /></td>';
        row += '<td style="padding-left:0px;padding-top:0px;padding-bottom:0px;padding-right:0px"><input class="form-control" id="TargetInfo_' + rowCount + '__TargetColor_V" name="TargetInfo[' + rowCount + '].TargetColor_V" type="text" value="' + TargetColor_V + '" /></td>';
        row += '<td style="padding-left:0px;padding-top:0px;padding-bottom:0px;padding-right:0px"><input class="form-control" id="TargetInfo_' + rowCount + '__D90Target_U" name="TargetInfo[' + rowCount + '].D90Target_U" type="text" value="' + D90Target_U + '" /></td>';
        row += '<td style="padding-left:0px;padding-top:0px;padding-bottom:0px;padding-right:0px"><input class="form-control" id="TargetInfo_' + rowCount + '__D90Target_V" name="TargetInfo[' + rowCount + '].D90Target_V" style="text-align:center;" type="text" value="' + D90Target_V + '" /></td>';
        row += '<td style="padding-left:0px;padding-top:0px;padding-bottom:0px;padding-right:0px"><input class="form-control" id="TargetInfo_' + rowCount + '__D10Target_U" name="TargetInfo[' + rowCount + '].D10Target_U" style="text-align:center;" type="text" value="' + D10Target_U + '" /></td>';
        row += '<td style="padding-left:0px;padding-top:0px;padding-bottom:0px;padding-right:0px"><input class="form-control" id="TargetInfo_' + rowCount + '__D10Target_V" name="TargetInfo[' + rowCount + '].D10Target_V" style="text-align:center;" type="text" value="' + D10Target_V + '" /></td>';
        row += '<td style="padding-left:0px;padding-top:0px;padding-bottom:0px;padding-right:0px"><input class="form-control" id="TargetInfo_' + rowCount + '__Thickness" name="TargetInfo[' + rowCount + '].Thickness" style="text-align:center;" type="text" value="' + Thickness + '" /></td>';
        row += '<td style="padding-left:0px;padding-top:0px;padding-bottom:0px;padding-right:0px"><input class="form-control" id="TargetInfo_' + rowCount + '__FilmWidth" name="TargetInfo[' + rowCount + '].FilmWidth" style="text-align:center;" type="text" value="' + FilmWidth + '" /></td>';
        row += '<td style="padding-left:0px;padding-top:0px;padding-bottom:0px;padding-right:0px"><input class="form-control" id="TargetInfo_' + rowCount + '__TargetDate" name="TargetInfo[' + rowCount + '].TargetDate" style="text-align:center;" type="text" value="' + TargetDate + '" /></td>';
        row += '<td style="padding-left:0px;padding-top:0px;padding-bottom:0px;padding-right:0px;vertical-align:top;"><img id="imgRemoveTarget" alt="Item Lookup" src="/Content/images/trashcan.png" style="cursor:pointer;width:32px;height:29px;" class="deleteLink" /></td>';
        row += '</tr>';

        //Hide the previous delete button
        $('#tbTargetDetails tr:last .deleteLink').hide('fast');

        $('#tbTargetDetails tr:last').after(row);
    }

Please help to solve my issue. Really appreciate your guys help. Thank you.

eikaz
  • 401
  • 8
  • 17
  • Are you binding your textboxes to a model? If so, why not just add the `[Required]` attribute to the property and have all this handled out of the box. And how are you dynamically adding the elements? –  Apr 24 '15 at 02:16
  • How is a new row being added - are you using the `BeginCollectionItem` helper, or manually adding the html? –  Apr 24 '15 at 02:24
  • i already add the required attribute the property but still no validation been done when i click the save button. – eikaz Apr 24 '15 at 02:25
  • How you generate the controls is important - see my last comment –  Apr 24 '15 at 02:26
  • 1
    You have multiple problems with your implementation. First, because you do not have in input for the indexer, if you delete a row, binding will fail. Next you don't add the necessary `data-val` attributes so there is nothing to validate. And if you did generate the correct html, all that is required is to reparse the validator. You also have a lot of unnecessary html (why would you add all those style attributes instead of just applying css? and what is the point of adding the `id` attribute?). I suggest you consider using the `BeginCollectionItem()` helper. –  Apr 24 '15 at 02:38
  • i'm newbie in mvc and jquery..can you help me to alter my code? For the style attribute, i will change it into css.. – eikaz Apr 24 '15 at 02:44
  • Give me an hour and then I'll add an answer showing how to make this work plus a link to using the `BeginCollectionItem()` helper. –  Apr 24 '15 at 02:48
  • No problem, but can you explain to me why you are passing things like `$('#txtTargetColorU').val()` to the method. Do you have a separate set of controls that you fill in and then add a new row based on those values? –  Apr 24 '15 at 02:55
  • i have add the new image to add rows in the table.. – eikaz Apr 24 '15 at 03:03
  • I suggest that the additional controls for adding a new row are not really necessary, and that clicking the `add` button should just add a new row to the table that the user can edit inline (and I will base my answer on that) –  Apr 24 '15 at 03:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/76139/discussion-between-eikaz-and-stephen-muecke). – eikaz Apr 24 '15 at 07:28

2 Answers2

8

You are not including the necessary data-val attributes to the textboxes, or the placeholder elements for displaying the validation messages, which are used by jquery.validate.unobtrusive.js to do client side validation. In addition, your current implementation does not allow the user to remove anything other that the last row which can be solved by including a hidden input for the indexer which allows non consecutive indexers to be posted and bound to your collection.

First start by adding one default ClsTargetInfo object to your TargetInfo property and generate its html in the view

<table id="table"> // add an id attribute
  <thead>.....</thead>
  <tbody is="tablebody"> // add an id attribute
    for(int i = 0; i < Model.TargetInfo.Count; i++)
    {
      <tr>
        <td>
          @Html.TextBoxFor(m => m.TargetInfo[i].TargetColor_U, new { id="", @class="form-control" }) // remove the unnecessary id attribute
          @Html.ValidationMessageFor(m => m.TargetInfo[i].TargetColor_U)
          // Add the following hidden input to only one column in the row
          <input type="hidden" name="TargetInfo.Index" value=@i />
        </td>
        <td>
          @Html.TextBoxFor(m => m.TargetInfo[i].TargetColor_V, new { id="", @class="form-control" }) // remove the unnecessary id attribute
          @Html.ValidationMessageFor(m => m.TargetInfo[i].TargetColor_V)
        </td>
        .... // other columns
      </tr>
    }
  </tbody>
</table>

Then inspect the html it generates for the <tr> element which should look something like

<tr>
  <td>
    <input data-val="true" data-val-required="The TargetColor_U field is required" name="TargetInfo[0].TargetColor_U" type="text" value="">
    <span class="field-validation-valid errorText" data-valmsg-for="TargetInfo[i].TargetColor_U" data-valmsg-replace="true"></span>
    <input type="hidden" name="TargetInfo.Index" value="0" />
  </td>
  ....
</tr>

and copy it inside a hidden element that is placed outside the form tags and replace all instance of the indexer with a dummy character so name="TargetInfo[0].TargetColor_U" becomes name="TargetInfo[#].TargetColor_U"), and also replace the value attribute of the hidden input so value="0" it becomes value="#"

<table id="newrow" style="display:none">
  .... // copy the tr element and its contents here
</table>

Then the script will look like

var form = $('form'); // or use the id if you have given the form an id
var newrow= $('#newrow');
var tablebody = $('#tablebody'); // modify to suit your id
$("#btnAddTarget").click(function() {
  var index = (new Date()).getTime(); // unique indexer
  var clone = newrow.clone(); // clone the new row
  clone.html($(clone).html().replace(/#/g, index)); // update the indexer of the clone
  var row = clone.find('tr');
  tablebody.append(row); // add the new row to the table
  // Reparse the validator
  form.data('validator', null);
  $.validator.unobtrusive.parse(form);
});

Side notes:

  1. Unobtrusive validation works by parsing the data-val attributes when the form is first rendered. When you add dynamic content, it is necessary to re-parse the validator as indicated in the last 2 lines of the script.
  2. The addition of the hidden input for the indexer allows you to delete any row in the collection, so removing the "delete" button is no longer necessary and will give the user a better experience.
  3. Rather that using inline styles, use css instead, for example, rather than <td style="padding-left:0px;padding-top:0px;padding-bottom:0px;padding-right:0px">, you should use #table td { padding: 0; } in your .css file
  4. While adding the rows purely client side gives the best performance, its difficult to maintain. If you add or change any validation attributes on your properties (for example you might later add a [StringLength] attribute), you will need to update the html to suit. As an alternative, you can consider using the BeginCollectionItem helper which means you have one partial view (representing a table row). For existing items, you use a foreach loop with @Html.Partial() and for new rows, you use ajax to call a controller method that return a the partial view, and update the DOM
  • i already try this but some issues happen..in the div, i just copy the all the tr element? it say element cannot be nested inside element
    – eikaz Apr 24 '15 at 07:07
  • 1
    Oops, sorry, you need to make it a `` (not a `
    `)
    –  Apr 24 '15 at 07:14
  • stephen..it works..just alter var row = clone.find('tr'); tablebody.append(row); but right now the appear at the bottom of the screen. how can i make it disappear?
    – eikaz Apr 24 '15 at 08:06
  • 1
    Just style it as hidden using css - `#newrow { display: none; }` (or inline using `` I have updated the answer –  Apr 24 '15 at 08:18
  • This works for me to a certain point, say my columns are Name and Age, and I add 4 rows, every name will equal what was input into the first row, and every Age will equal what was put into the first row. – JsonStatham Jan 29 '16 at 16:37
  • @selectDistinct. That's not possible unless you are doing something wrong. When I get a chance I will create a DotNetFiddle to show you how it works. –  Jan 29 '16 at 23:56
  • @selectDistinct, Did you see the [DotNetFiddle](https://dotnetfiddle.net/UjxtUW) I created for you in my answer to your [question](http://stackoverflow.com/questions/35084076/mvc-make-list-property-required-on-first-loop-only)? –  Feb 01 '16 at 09:35
0
<script src="Scripts/jquery-1.10.2.min.js"></script>
<script type="text/javascript">
    $(document).ready(function () {

        function DeleteRow(btn) {

            //alert("delete" + btn);
            var tr = btn.closest('tr');
            tr.remove();
        }
        $(".btnsd").click(function () {
            // debugger;
            alert("hjkghk");
            divs = $('.val')
            for (ind in divs) {
                var div = divs[ind];
                if (div.value == "") {
                    div.focus();
                    return false;
                }
            }
            $('#Employertbl').append(
                                    '<tr>' +
                                        '<td> @Html.TextBox("item.employer_name", null, new { @class = "form-control val" })</td>' +
                                        '<td width="24%"> <div style="float:left; padding-right:5px;">@Html.TextBox("item.duration_From", null, new { @id = "", @placeholder = "From Date", @class = "form-control input-date datepicker val", @readonly = true })</div> ' +
                                              '<div>@Html.TextBox("item.duration_to", null, new { @id = "", @class = "form-control input-date datepicker val", @placeholder = "To Date", @readonly = true })</div></td>' +
                                        '<td> @Html.TextBox("item.designation", null, new { @class = "form-control val" })</td>' +
                                        '<td> @Html.TextBox("item.worked_skill", null, new { @class = "form-control val" })</td>' +
                                        '<td> @Html.TextBox("item.proj_handled", null, new { @class = "form-control val" })</td>' +
                                        '<td>  @Html.CheckBox("item.current_employed",new{@class = "current" })</td>' +
                                        '<td><input type="button" value="Remove" onclick="DeleteRow(this)" name="delete" class="btn blue pull-right" /> </td>' +
                                    '</tr>'
                                    );



        });
    });
</script>


<div class="table-responsive">
    <table id="Employertbl" class="table table-striped table-bordered table-hover dataTable no-footer">
        <tbody>
            <tr>
                <th>Employer Name</th>
                <th>Duration</th>
                <th>Designation</th>
                <th>Worked skill(s)</th>
                <th>Reason of change</th>
                <th>Currently Employed</th>
                <th>Action</th>
            </tr>

            <tr>
                <td>
                    <input class="form-control val" id="item_employer_name" name="item.employer_name" type="text" value="">
                </td>
                <td width="24%">
                    <div style="float:left; padding-right:5px;"><input class="form-control input-date datepicker val hasDatepicker" name="item.duration_From" placeholder="From Date"  type="text" value="" id="dp1459328857835"></div>
                    <div> <input class="form-control input-date datepicker val hasDatepicker" name="item.duration_to" placeholder="To Date"  type="text" value="" id="dp1459328857836"></div>

                </td>
                <td>
                    <input class="form-control val" id="item_designation" name="item.designation" type="text" value="">
                </td>
                <td>
                    <input class="form-control val" id="item_worked_skill" name="item.worked_skill" type="text" value="">
                </td>
                <td>
                    <input class="form-control val" id="item_proj_handled" name="item.proj_handled" type="text" value="">
                </td>
                <td>
                    <input class="current" id="item_current_employed" name="item.current_employed" type="checkbox" value="true"><input name="item.current_employed" type="hidden" value="false">
                </td>
                <td>
                    <input id="myButton" type="button" value="add"  name="delete" class="btnsd bcbn">
                </td>
            </tr>


            <tr><td> <input class="form-control val" id="item_employer_name" name="item.employer_name" type="text" value=""></td><td width="24%"> <div style="float:left; padding-right:5px;"><input class="form-control input-date datepicker val hasDatepicker" name="item.duration_From" placeholder="From Date"  type="text" value="" id="dp1459328857837"></div> <div><input class="form-control input-date datepicker val hasDatepicker" name="item.duration_to" placeholder="To Date" type="text" value="" id="dp1459328857838"></div></td><td> <input class="form-control val" id="item_designation" name="item.designation" type="text" value=""></td><td> <input class="form-control val" id="item_worked_skill" name="item.worked_skill" type="text" value=""></td><td> <input class="form-control val" id="item_proj_handled" name="item.proj_handled" type="text" value=""></td><td>  <input class="current" id="item_current_employed" name="item.current_employed" type="checkbox" value="true"><input name="item.current_employed" type="hidden" value="false"></td><td><input type="button" id="myButton" value="add" name="delete" class="btnsd dfsd"> </td></tr>
            <tr><td> <input class="form-control val" id="item_employer_name" name="item.employer_name" type="text" value=""></td><td width="24%"> <div style="float:left; padding-right:5px;"><input class="form-control input-date datepicker val hasDatepicker" name="item.duration_From" placeholder="From Date" type="text" value="" id="dp1459328857839"></div> <div><input class="form-control input-date datepicker val hasDatepicker" name="item.duration_to" placeholder="To Date"  type="text" value="" id="dp1459328857840"></div></td><td> <input class="form-control val" id="item_designation" name="item.designation" type="text" value=""></td><td> <input class="form-control val" id="item_worked_skill" name="item.worked_skill" type="text" value=""></td><td> <input class="form-control val" id="item_proj_handled" name="item.proj_handled" type="text" value=""></td><td>  <input class="current" id="item_current_employed" name="item.current_employed" type="checkbox" value="true"><input name="item.current_employed" type="hidden" value="false"></td><td><input type="button" id="myButton" value="add" name="delete" class="btnsd"> </td></tr>
        </tbody>
    </table>
</div>
  • Welcome to Stack Overflow! While this answer is probably correct and useful, it is preferred if you [include some explanation along with it](http://meta.stackexchange.com/q/114762/159034) to explain how it helps to solve the problem. This becomes especially useful in the future, if there is a change (possibly unrelated) that causes it to stop working and users need to understand how it once worked. Thanks! – Hatchet Mar 30 '16 at 14:02