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 class="error-span" style="color:red"></span> <div id="main_container"> <i onclick="createTodo()" id="create-button" class="fas fa-plus-circle" 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 class="todo" asp-action="Edit" asp-controller="Todos" data-id="@todo.Id"> <input type="hidden" asp-for="@todo.Id" id="id_@todo.Id" /> <div class="todo-up todo-row"> <textarea autocomplete="off" placeholder="Put the title here..." class="todo-header" asp-for="@todo.Title" id="title_@todo.Id" ></textarea> <textarea autocomplete="off" placeholder="Put the description here..." class="todo-description" asp-for="@todo.Description" id="decription_@todo.Id" ></textarea> </div> <div class="todo-down todo-row"> <div class="todo-validation-row"> <span></span> <i class="fa-regular fa-check todo-edit" alt="Saved"></i> <span class="tooltip-text">Saved</span> @*Tooltip for edition*@ </div> <div class="todo-footer"> <div class="todo-updated"><img src="~/assets/img/update.svg" alt="Updated at" /><span>@todo.UpdatedDate</span></div> <a onclick="showDeleteModal(@todo.Id)" title="Delete todo"> <i class="fas fa-trash"></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
Advertisement
Answer
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.