Home > Net >  When one form is updated (using Ajax) reloading the forms make them have the same values as the prev
When one form is updated (using Ajax) reloading the forms make them have the same values as the prev

Time:12-15

I'm trying to make a simple Todo app to learn asp net core mvc.

I did the CRUD to manage the todos and it worked fine. For the next step i wanted to try adding Ajax to it (avoiding to reload the entire page), delete worked fine, create too, but when i want to edit one todo (which is basically a form) the response of the Ajax request sets all the inputs of all the todos at the same value.

If I update "Buy chocolat" by "Buy chocolate" as the title of one todo, all other todos will have a title "Buy chocolate".

If I refresh the page (or just the section containing todos) everything goes back to normal, which means the database updated just the todo I wanted to.

It's really weird and it probably comes from the fact that the inputs have the same name value (todo 1 title => todo.Title, todo 2 title => todo.Title, etc...) even though it works fine for all the rest.

Here's the page with the container of todos :

@model IEnumerable<TodoApp.Models.Todo>

@section Css{
    <link href="/css/todos.css" rel="stylesheet" />
    <link href="~/lib/fontawesome/css/all.css" rel="stylesheet" />
}

@{
    ViewData["Title"] = "List of todos";
}

<h1>My list of Todos</h1>

<span  style="color:red"></span>

<div id="main_container">
    
    <i onclick="createTodo()" id="create-button"  title="Add new todo"></i>

    <div id="todos_container">
        @await Html.PartialAsync("_TodoList", Model)
    </div>
</div>

<partial name="_DeleteModal">

@section Scripts{
    <script src="~/js/todos.js"></script>
}

Here's the foreach that displays all todos which also is the partial view "_TodoList" :

@model IEnumerable<TodoApp.Models.Todo>

@foreach (Todo todo in Model)
{
    <form  asp-action="Edit" asp-controller="Todos" data-id="@todo.Id">
        <input type="hidden" asp-for="@todo.Id" id="[email protected]" />
        <div >
            <textarea autocomplete="off" placeholder="Put the title here..."  asp-for="@todo.Title" id="[email protected]" ></textarea>
            <textarea autocomplete="off" placeholder="Put the description here..."  asp-for="@todo.Description" id="[email protected]" ></textarea>
        </div>
        <div >
            <div >
                <span></span>
                <i  alt="Saved"></i>
                <span >Saved</span> @*Tooltip for edition*@
            </div>
            <div >
                <div ><img src="~/assets/img/update.svg" alt="Updated at" /><span>@todo.UpdatedDate</span></div>
                <a onclick="showDeleteModal(@todo.Id)" title="Delete todo">
                    <i ></i>
                </a>
            </div>
        </div>
    </form>
}

The beginning of the controller method :

[HttpPatch]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit([Bind("Id", "Title", "Description")] Todo todo)
        {
            if (ModelState.IsValid)
            {
                var matchingTodo = await _context.Todos.FindAsync(todo.Id);
                if (matchingTodo != null)
                {
                    if (GetConnectedUserId() == matchingTodo.UserId)
                    {
                        matchingTodo.Title = todo.Title;
                        matchingTodo.Description = todo.Description;
                        matchingTodo.UpdatedDate = DateTime.Now;
                        _context.Update(matchingTodo);
                        await _context.SaveChangesAsync();
                        var todos = GetTodosOfConnectedUser();
                        var partialView = PartialView("_TodoList", todos);
                        return partialView;

The GetTodosOfConnectedUser method (which return an Enumerable object of Todos that belongs to the user currently connected) :

private IEnumerable<Todo> GetTodosOfConnectedUser()
{
            return _context.Todos.Where(t => t.UserId == Convert.ToInt32(HttpContext.User.FindFirst("user_id").Value)).OrderByDescending(t => t.UpdatedDate);
}

And the JS for the Ajax request :


${'.todo'}.on("change", function (ev) {
        let form = ev.currentTarget;
        editTodo(form);
    });

function editTodo(form) {
    try {
        $.ajax({
            type: 'PATCH',
            url: form.action,
            data: new FormData(form),
            processData: false,
            contentType: false,
            success: function (res) {
                $(".error-span").html("");
                $('#todos_container').html(res);
            },
            error: function (err) {
                console.log(err);
                $(".error-span").html("An error occured please try again.");
            }
        });
        return false;
    }
    catch (ex) {
        console.log(ex);
    }
}

Thank you for your time

CodePudding user response:

So, the problem is weird. Like, really weird.

I have followed what's happening step-by-step and everything is going smoothly and then... All the forms get the same inputs/textareas for no apparent reasons.

I believe it comes from the fact that I create one form for each todo, which is a really bad practice, probably never meant to be done in the first place. If you ever encounter this problem, just change the way you do it.

CodePudding user response:

I show my code and hope it can help you :

model

    public class Todo
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public string Description { get; set; }
        public string UpdatedDate { get; set; }
    }

view

@model IEnumerable<TodoApp.Models.Todo>

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

<h1>Index</h1>

<div id="todos_container">
    @foreach (Todo todo in Model)
{
    <form  asp-action="Edit" asp-controller="Todos" data-id="@todo.Id">
        <input type="hidden" asp-for="@todo.Id" id="[email protected]" />
        <div >
            <textarea autocomplete="off" placeholder="Put the title here..."  asp-for="@todo.Title" id="[email protected]" ></textarea>
            <textarea autocomplete="off" placeholder="Put the description here..."  asp-for="@todo.Description" id="[email protected]" ></textarea>
        </div>
        <div >
            <div >
                <span></span>
                <i  alt="Saved"></i>
                <span >Saved</span> @*Tooltip for edition*@
            </div>
            <div >
                <div ><img src="~/assets/img/update.svg" alt="Updated at" /><span>@todo.UpdatedDate</span></div>
                <a onclick="showDeleteModal(@todo.Id)" title="Delete todo">
                    <i ></i>
                </a>
            </div>
        </div>
    </form>
}
</div>

@*@section Scripts {*@
    <script>
        $('.todo').on("change", function (ev) {
           
            let form = ev.currentTarget;
            editTodo(form);
        });

    function editTodo(form) {
        try {
            $.ajax({
                type: 'PATCH',
                url: '/todoes/Edit',
                data: new FormData(form),
                processData: false,
                contentType: false,
                success: function (res) {
                    $(".error-span").html("");
                    $('#todos_container').html(res);
                },
                error: function (err) {
                    console.log(err);
                    $(".error-span").html("An error occured please try again.");
                }
            });
            return false;
        }
        catch (ex) {
            console.log(ex);
        }
    }
    </script>
@*}*@

action

        [HttpPatch]
        [ValidateAntiForgeryToken]
        public async Task<IActionResult> Edit([Bind("Id", "Title", "Description")] Todo todo)
        {
            if (ModelState.IsValid)
            {
                var matchingTodo = await _context.Todos.FindAsync(todo.Id);
                if (matchingTodo != null)
                {
                        matchingTodo.Title = todo.Title;
                        matchingTodo.Description = todo.Description;
                        matchingTodo.UpdatedDate = DateTime.Now.ToString();
                        _context.Update(matchingTodo);
                        await _context.SaveChangesAsync();
                        var todos = GetTodosOfConnectedUser();
                        var partialView = PartialView("_TodoList", todos);
                        return partialView;
                    
                }
                return BadRequest();
            }
            return View(todo);
        }

enter image description here

  • Related