0

I am trying to create a simple counter for a dynamic table I am creating. Essentially, every time I add a row to the table I want the counter to increase by 1. I'm trying to avoid adding some arbitrary property to the model if I can avoid it but I am really scratching my head at how to get this to work.

My table starts with 2 rows and is model-bound to a list. The intent here is to allow every new row after that to be given a new index so it can be submitted with the form and create the list as I go (one submit action further down in the view, the addRow() function is called with a button that does not submit the form)

Essentially here's what I have in my view

@model AddUsers
@{
    ViewData["Title"] = "Add Users";
    var Counter = 2;
}

@section Scripts{

<script>
    function addCount() {
        var count = @Counter;
        console.log('count: ' + count + 'counter: ' + '@Counter');
        count = count + 1;
        @Counter = count;
        console.log('count: ' + count + 'counter: ' + '@Counter');
    }
</script>

    <script>
        function addRow() {
            var counter = @Counter;
            var table = document.getElementById("AddUsersTable");
            var row = table.insertRow(-1);
            var cell1 = row.insertCell(0);
            cell1.innerHTML = '<input type="text" asp-for="Users[' + counter + '].FirstName"/><br /><span asp-validation-for="Users[' + counter + '].FirstName class="text-danger"></span>';
            var cell2 = row.insertCell(1);
            cell2.innerHTML = '<input type="text" />';
            var cell3 = row.insertCell(2);
            cell3.innerHTML = '<input type="text" />';
            var cell4 = row.insertCell(3);
            cell4.innerHTML = '<input type="text" />';
            addCount();
        }
    </script>
}

When I debug this and view the log and elements in the browser, I see the following.

Inspect Element Results

I am clearly missing something crucial as none of this is working as expected.

What should have been a simple counter is turning out to be a bigger headache than I anticipated. I tried some of the answers and comments from here as well as my own tinkering to no avail.

1 Answer 1

1

It seems you want to add the count to number the name.

Change like below:

@model AddUsers
<button onclick="addRow()">Add Row</button>
<table id="AddUsersTable">
    <tr>
        <th>UserName</th>
        <th>1</th>
        <th>2</th>
        <th>3</th>
    </tr>
    <tr>
        <td>Candy</td>
        <td>aaa1</td>
        <td>bbb1</td>
        <td>ccc1</td>
    </tr>
</table>
@section Scripts{
    <script>
        var counter = 2;//it must define outside the function
        function addRow() {            
            var table = document.getElementById("AddUsersTable");
            var row = table.insertRow(-1);
            var cell1 = row.insertCell(0);
            cell1.innerHTML = '<input type="text" asp-for="Users[' + counter + '].FirstName"/><br /><span asp-validation-for="Users[' + counter + '].FirstName class="text-danger"></span>';
            var cell2 = row.insertCell(1);
            cell2.innerHTML = '<input type="text" />';
            var cell3 = row.insertCell(2);
            cell3.innerHTML = '<input type="text" />';
            var cell4 = row.insertCell(3);
            cell4.innerHTML = '<input type="text" />';
            counter++;
        }
    </script>
}

Result: enter image description here

UPDATE:

1.Model:

public class AddUsers
{
    public int Id { get; set; }
    [Required]
    public string Name { get; set; }
    [Required]
    public int Age { get; set; }
}

2.Index.cshtml:

I suggest that you could add _ValidationScriptsPartial,it exists in your template by default and it contains jquery-validate and jquery-validation-unobtrusive.This makes you can validate on client side instead of validating ModelState on server side.

@model IEnumerable<AddUsers>

@{
    ViewData["Title"] = "Index";
}

<button onclick="ShowPartial()">Add Row</button>

<table class="table">
    <thead>
        <tr>
            <th>
                @Html.DisplayNameFor(model => model.Name)
            </th>
            <th>
                @Html.DisplayNameFor(model => model.Age)
            </th>

        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @Html.DisplayFor(modelItem => item.Name)
                </td>
                <td>
                    @Html.DisplayFor(modelItem => item.Age)
                </td>
            </tr>
        }
    </tbody>
</table>
<div id="CreateUserPartial" hidden>
    @await Html.PartialAsync("PartialView", new AddUsers())
</div>
@section Scripts
{
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
    <script>
        function ShowPartial() {
            $('#CreateUserPartial').removeAttr('hidden');
        }
    </script>
}

3.Partial View(Located at Views/Shared/PartialView.cshtml):

@model AddUsers

<form asp-action="Create">
    <div class="form-group">
        <label asp-for="Name" class="control-label"></label>
        <input asp-for="Name" class="form-control" />
        <span asp-validation-for="Name" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Age" class="control-label"></label>
        <input asp-for="Age" class="form-control" />
        <span asp-validation-for="Age" class="text-danger"></span>
    </div>
    <div class="form-group">
        <input type="submit" value="Create" class="btn btn-primary" />
    </div>
</form>

4.Controller:

public class AddUsersController : Controller
{
    private readonly YourContext _context;

    public AddUsersController(YourContext context)
    {
        _context = context;
    }      
    // GET: AddUsers
    public async Task<IActionResult> Index()
    {
        return View(await _context.Users.ToListAsync());
    }
    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create([Bind("Id,Name,Age")] AddUsers addUsers)
    {
        if (ModelState.IsValid)
        {
            _context.Add(addUsers);
            await _context.SaveChangesAsync();
            return RedirectToAction(nameof(Index));
        }
        return View(addUsers);
    }
}

Result: enter image description here

Sign up to request clarification or add additional context in comments.

7 Comments

This looks great! The reason I was looking for it to be stored outside of the script is because when the user submits the form with invalid values (say they omit a required field like firstname or lastname), the view resets and the validation messages just show up above the table and the newly created rows are gone. Any ideas on how to keep rows after they're created and show the validation messages within the table once the view is returned?
Also, after trying it, it appears that once the form is submitted, the Users list only contains the first two rows, none of the dynamic ones are added. I will look into both of these issues but if you have any ideas that would be great.
Before coding,I want to check your requirement,did you want a button AddRow and then add empty inputs with one raw.Then you could fill data to the empty input.If modelstate is not valid,the view could show the error message,and if the modelstate is valid,the data would add to the view.
If so,did you consider using bootstrap modal or a partial view?The js event could not keep the added row after the server side action.So,i suggest that you could use a modal or a partial view to show the form separately.And no need to make a counter to specify the name.
Yes, essentially here is the workflow. User clicks "Add Row" for an additional user to be entered. There's a submit button on the page and once all users have been entered, the model is inspected for validity and if any first or last name fields are missing, the model validation prevents them from submitting the form and are redirected back to the form with validation messages present but also all dynamic rows & values entered in all rows are still present and will correctly bind to the list in the model.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.