I have a timesheet application that has a View where the user can select customers and tasks and add them to a dynamic table. This table is filled with the tasks and input fields for filling in hours worked.
For adding the new tasks in the dynamic table I use jQuery, so the savenewtask button is not a submit button. Instead I have a proper submit button for saving the hours when filled in.
The View is strongly typed to a model called TimesheetViewModel (see below). The controller passes the model to the View, and then the input fields are bound to properties in the model.
However, when I submit with the submit button and try to update the model in the Controller it doesn't update. It seemed from the Nerddinner tutorial (which I am using to learn MVC) that the model should automatically be updated using the values from the forms fields it had been bound to when you use UpdateModel(). But it doesn't. What am I doing wrong?
Here is all the relevant code:
View:
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<script src="../../Scripts/jquery-1.4.1.js" type="text/javascript"></script>
<script src="../../Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
//Hook onto the MakeID list's onchange event
$("#CustomerId").change(function () {
//build the request url
var url = "Timesheet/CustomerTasks";
//fire off the request, passing it the id which is the MakeID's selected item value
$.getJSON(url, { id: $("#CustomerId").val() }, function (data) {
//Clear the Model list
$("#TaskId").empty();
//Foreach Model in the list, add a model option from the data returned
$.each(data, function (index, optionData) {
$("#TaskId").append("<option value='" + optionData.Id + "'>" + optionData.Name + "</option>");
});
});
}).change();
});
</script>
<h2>Index</h2>
<% using (Html.BeginForm())
{%>
<%: Html.ValidationSummary(true) %>
<fieldset>
<legend>Fields</legend>
<div>
<label for="Customers">
Kund:</label>
<%:Html.DropDownListFor(m => m.Customers, new SelectList(Model.Customers, "Id", "Name"), "Välj kund...", new { #id = "CustomerId" })%>
<label for="Tasks">
Aktiviteter:</label>
<select id="TaskId">
</select>
</div>
<p>
<input type="button" value="Save new task" id="savenewtask" />
</p>
<table width="100%">
<%--<% foreach (var task in Model.Tasks)--%>
<% foreach (var task in Model.WeekTasks)
{ %>
<tr>
<td>
<%: task.Customer.Name %>
</td>
<td>
<%: task.Name %>
</td>
<td>
<% foreach (var ts in task.TimeSegments)
{ %>
<input class="hourInput" type="text" size="2" id="<%: ts.Task.CustomerId + '_' + ts.TaskId + '_' + ts.Date %>"
value="<%: ts.Hours %>" />
<% } %>
</td>
</tr>
<% } %>
</table>
<input type="submit" value="Save hours" id="savehours" />
</fieldset>
<% } %>
</asp:Content>
From the Controller:
private TimesheetViewModel _model;
public TimesheetController()
{
_model = new TimesheetViewModel();
}
public ActionResult Index()
{
return View(_model);
}
[HttpPost]
public ActionResult Index(FormCollection collection)
{
try
{
UpdateModel(_model);
_model.Save();
return View(_model);
//return RedirectToAction("Index");
}
catch
{
return View();
}
}
The ViewModel:
public class TimesheetViewModel
{
private TimesheetContainer _model; //TimesheeContainer is an Entity Framework model
public TimesheetViewModel()
{
_model = new TimesheetContainer();
}
public IList<Customer> Customers
{ get { return _model.Customers.ToList(); } }
public IList<Task> Tasks
{ get { return _model.Tasks.ToList(); } }
public IList<Task> WeekTasks
{
get
{
//Get the time segments for the current week
DateTime firstDayOfWeek = DateTime.Parse("2010-12-05");
DateTime lastDayOfWeek = DateTime.Parse("2010-12-13");
List<TimeSegment> timeSegments = new List<TimeSegment>();
foreach (var timeSegment in _model.TimeSegments)
{
if(timeSegment.DateTimeDate > firstDayOfWeek && timeSegment.DateTimeDate < lastDayOfWeek)
timeSegments.Add(timeSegment);
}
//Group into tasks
var tasks = from timeSegment in timeSegments
group timeSegment by timeSegment.Task
into t
select new { Task = t.Key };
return tasks.Select(t => t.Task).ToList();
}
}
public IList<TimeSegment> TimeSegments
{ get { return _model.TimeSegments.ToList(); } }
public void Save()
{
_model.SaveChanges();
}
public void AddTimeSegments(Task task)
{
_model.AddToTasks(task);
_model.SaveChanges();
}
}
Partial class to get tasks for a specific week (only dummy week at this time for testing):
public partial class TimeSegment
{
public DateTime DateTimeDate
{ get { return DateTime.Parse(Date); } }
}
Why is the model not updating, and what can I change to make it work?
Put a breakpoint on your first ActionResult Index(), is that getting called when you do the submit? you may need [HttpGet] on it, otherwise I think it gets both.
Related
I am trying to find a way to send the id of the clicked button to the backend. The problem is that I am creating lots of buttons with one method but the id is different.
#foreach (var item in Model.showManager.GetMovies())
{
i++;
#if (Model.user.IsAdmin == true)
{
<input class="btn_confirm" type="submit" id=i value="Delete"/>
}
}
The point is that every button is created with different id and I want to send that id to the backend.
Update
My demo is a MVC project, I have a DynamicButtonController and a Index view:
DynamicButtonController:
public class DynamicButtonController : Controller
{
public IActionResult Index()
{
return View();
}
[HttpPost]
public IActionResult Index(int id)
{
return View();
}
}
Index view :
#for (var i = 0; i < 5;i++ )
{
<input class="btn_confirm" type="submit" id=#i value="Delete" />
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
$(".btn_confirm").click(function()
{
var data = (this).id;
$.ajax({
type: "POST",
url: '/DynamicButton/Index/',
data: { id: data }
});
});
</script>
result:
If you use Razor pages, you can refer to the below demo,use asp-route-id="#i"
ButtonIdModel:
public class ButtonIdModel : PageModel
{
public void OnGet()
{
}
public void OnPost(string id)
{
}
}
ButtonId.cshtml:
#page
#model yourproject.Pages.ButtonIdModel
<form method="post">
#for (var i = 0; i < 5;i++ )
{
<input class="btn_confirm" type="submit" id=#i value="Delete" asp-route-id="#i" />
}
</form>
The point is that every button is created with different id and I want
to send that id to the backend.
Well, based on your issue, you want to bind all the button ids then want to pass those Ids in your backend.
However, another answer has guided you how to pass id to your controller. Nonetheless, it doesn't resolve your main concern that is how to pass the list of ids on button submit.
Algorithm:
As said earlier, first you have to get the list of button ids which has been generated from your foreach loop and you have to push them in an array, finally need to pass those in your controller (backend). Here, importantly you have to keep in mind, it doesn't matter how the button been generated, for loop or foreach loop the fact is your button should have class name of same type and the ids for instance: class="myBtnClass btn btn-danger" and id="btnId:#i"
Solution:
View:
#{
ViewData["Title"] = "ViewGetDynamicButtonsID";
}
<div>
#for (var i = 1; i < 5; i++)
{
<input class="myBtnClass btn btn-danger" id="btnId:#i" value="Delete:#i" style="margin-bottom:2px" /> <br />
}
<input type="submit" id="btnSubmit" class="btn btn-success" value="Submit" />
</div>
#section scripts {
<script src="https://ajax.aspnetcdn.com/ajax/jQuery/jquery-3.2.1.min.js"></script>
<script>
$(document).ready(function () {
$("#btnSubmit").on("click", function () {
var ids = [];
$(".myBtnClass").each(function () {
//Getting All Button Ids and Pusing in array
ids.push($(this).attr("id"));
});
$.ajax({
type: "POST",
url: 'http://localhost:5094/stuff/GetAllButtonId',
datatype: "json",
data: { buttonids: ids },
success: function (res) {
console.log(res);
alert("It works")
},
error: function () {
alert("It failed");
}
});
return false;
});
});
</script>
}
Controller:
public IActionResult CreateDynamicButton()// This is for loading the view
{
return View();
}
[HttpPost]
public IActionResult GetAllButtonId(List<string> buttonids) // This for submit the button request.
{
return Ok(buttonids);
}
Note: I have defined Button Ids as List<string> thus you can do it as your convenient type
Output:
This is a check & mate situation for me...
I am using mvc 3.
I am trying to make a post and comment module on a single view. below is the code for the view and controller. I am able to get the post and all the comments on load but once I add a new comment through an AJAX call its is saved to the correct table in DB but I am not understanding how to update it on view without refreshing the page...
//model
public class PostViewModel
{
public bool? IsActive
{ get; set; }
public string PostDescription
{ get; set; }
...
public List<PostCommentModel> objPostCommentInfo { get; set; }
}
//Post Controller
DBEntities1 db = new DBEntities1();
public ActionResult Index(int ID)
{
int id = Convert.ToInt32(ID);
PostViewModel objPostViewModel = new PostViewModel();
List<PostViewModel> lstobjPostViewModel = new List<PostViewModel>();
PostCommentModel objPostCommentModel;
List<PostCommentModel> lstobjPostCommentModel = new List<PostCommentModel>();
var objPost = (from x in db.PostInfoes
where x.PostId == id
select x).ToList();
var objPostComment = (from y in db.PostCommentInfoes
where y.PostId == id
orderby y.CommentId descending
select y).ToList();
foreach (var x in objPost)
{
objPostViewModel.PostID = x.PostId;
objPostViewModel.IsActive = x.IsActive;
objPostViewModel.PostTitle = x.PostTitle;
objPostViewModel.PostDescription = x.PostDescription;
lstobjPostViewModel.Add(objPostViewModel);
}
foreach (var y in objPostComment)
{
objPostCommentModel = new PostCommentModel();
objPostCommentModel.PostId = y.PostId;
objPostCommentModel.IsActive = y.IsActive;
objPostCommentModel.CommentBody = y.CommentBody;
lstobjPostCommentModel.Add(objPostCommentModel);
}
objPostViewModel.objPostCommentInfo = lstobjPostCommentModel;
return View(lstobjPostViewModel);
}
//view
#model IEnumerable<MVCProjectModels.PostViewModel>
<table border="1">
#foreach (var item in Model)
{
<tr>
<td>
<text>Created By:</text>
#Html.DisplayFor(modelItem => item.PostDescription)
</td>
<td rowspan="2">
#Html.DisplayFor(modelItem => item.PostDescription)
</td>
</tr>
.....
}
</table>
<table>
<tr>
<td>
<textarea cols="10" rows="5" id="txtComment"></textarea>
</td>
</tr>
<tr>
<td>
<input id="btnPostComment" type="button" value="Post Comment" />
</td>
</tr>
</table>
<table border="1">
#foreach (var item1 in Model)
{
foreach (var item2 in item1.objPostCommentInfo)
{
<tr>
<td colspan="2">
#Html.DisplayFor(modelItem => item2.CommentBody)
</td>
</tr>
}
}
</table>
//Ajax call to update the comment (The comments gets saves to the database but I am not finding anyway to update it on the UI or View)
<script type="text/javascript">
$("#btnPostComment").click(function () {
var commentBody = $("#txtComment").val();
postComment(commentBody);
});
function postComment(commentBody) {
$.ajax({
url: "/Post/postComment", // this controller method calls a store procedure to insert the new comment in the database.
type: 'POST',
data: {
Comment: commentBody,
ID: 6
},
success: function (result) {
},
error: function () {
alert("error");
}
});
}
</script>
Please let me know if I am doing any major designing mistakes in the above module. I am new to mvc so just trying to do this by reading some books and articles so not sure if this is correct way of achieving such results. thanks
You need to name your table for easier reference:
<table border="1" id="postList">
On your view you are writing a name of a user <text>Created By:</text> but I don't see that in the model. So assuming that is saved in a session or you can retrieve it in your controller you can do something like:
public ActionResult PostComment(YourModel input){
// everything went well
// you get this from a session or from the database
var username = "the creator";
return Json(new { success = true, username});
}
On success of your ajax call:
success: function (result) {
if (result.success) {
$("#postList").append('<tr><td><text>Created By:</text>' +
result.username + '</td><td rowspan="2">' +
commentBody + '</td>');
</tr>
}
}
It will be cool though if instead of concatenating the tr string that you read it from a template and insert the necessary values. Or you can use other tools like knockout to do the binding on the client side. But that is for another question I guess.
You could just .prepend() the new comment text to the comments table in the success callback of your AJAX call:
success: function (result) {
// give your comments table a class="comments" so that the following
// selector is able to match it:
$('table.comments').prepend(
$('<tr/>', {
html: $('<td/>', {
text: commentBody
})
})
);
}
I'm experiencing current error in my view:
<%# Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<ProjectenII.Models.Domain.StudentModel>"%>
<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
IndexStudents
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
<h2>IndexStudents</h2>
<%using (Html.BeginForm()) { %>
<%=Html.ListBoxFor(model => model.NormalSelected, new MultiSelectList(Model.NormalStudentsList, "StudentNummer", "Naam", Model.NormalSelected), new { size = "6" }); %>
<input type="submit" name="add"
id="add" value=">>" /><br />
<input type="submit" name="remove"
id="remove" value="<<" />
<%=Html.ListBoxFor(model => model.NoClassSelected, new MultiSelectList(Model.StudentsNoClassList, "StudentNummer", "Naam", Model.NoClassSelected)); %>
<% } %>
<%=Html.HiddenFor(model => model.Save) %>
<input type="submit" name="apply" id="apply" value="Save!" />
</asp:Content>
It gives me an error at the listboxfor() method... saying ") expected".
But I close all the opening tags... very strange though!
What I want to use it for: I want to move items from one listbox to the other and then update the database. So I'd like to do it using formCollection, unless there is another way?
Students have a field named "classID", when I update the database, that value needs to change from the current value to "0". I think the best way is using formCollections? Isn't it?
This is my StudentModel
public class StudentModel
{
public IEnumerable<Student> NormalStudentsList { get; set; }
public IEnumerable<Student> StudentsNoClassList { get; set; }
public string[] NormalSelected { get; set; }
public string[] NoClassSelected { get; set; }
public string Save { get; set; }
}
Controller:
public ActionResult IndexStudents(Docent docent, int id, int klasgroepid)
{
var studentModel = new StudentModel
{
NormalStudentsList = docent.GeefStudenten(id, klasgroepid),
StudentsNoClassList = docent.GeefStudenten(id, klasgroepid)
};
return View(studentModel);
}
I have two questions: how can I fix the error? AND how can I update the database?
I suggest using "UpdateModel()" ... ?
Thanks in advance!!
Not sure what your second question is because you didn't include the code you're using to persist your model to the database.
The ")" expected error is because you have a semicolon at the end of your ListBoxFor method call.
It should look like this:
<%=Html.ListBoxFor(model => model.NormalSelected, new MultiSelectList(Model.NormalStudentsList, "StudentNummer", "Naam", Model.NormalSelected), new { size = "6" }) %>
When you use <%= you don't need the semicolon.
I really didn't know what title to give this question, but I'll explain here:
I have a View with a bunch of input fields in a table. Each row in the table represents a task, and each column a weekday, and the input fields in each cell are there to let the user input hours worked for that task and day.
I then have a submit button to post the hours when the user wants to save. But here's the problem: Each timesegment (as the object that holds hours is called) also has the property Description, to let the user write a description of what has been done for a particular time segment reported.
So how could I get the description property for the selected timesegment input field and show it in another "description" input field, and then let the user modify the description and save it with the timesegment?
Here's what I've done so far:
Action method to get the description:
public ActionResult GetDescription(string name, int number, int year)
{
try
{
int taskId = Int32.Parse(name.SubstringAfter("Tasks[").Substring(0, 1));
int timeSegmentId = Int32.Parse(name.SubstringAfter("CurrentTimeSegments[").Substring(0, 1));
List<Task> tasks = _repository.GetCurrentTasks(number, year);
var description = tasks[taskId].CurrentTimeSegments[timeSegmentId].Description;
return Content(description);
}
catch (Exception)
{
return Content("");
}
}
jQuery:
function getDescription() {
$('.hourInput').focus(function () {
var name = $(this).attr('name');
var number = '<%: Model.WeekNumber %>';
var year = '<%: Model.Year %>';
var url = '<%=Url.Action("GetDescription", "Timesheet") %>';
$.get(url, { name: name, number: number, year: year }, function (data) {
$('#description').val(data);
});
});
}
Now, as you can see, I have to parse the name attribute of the input field to get the object I'm after, and this seems like a bit of a hack... But it's the only way I can see to get this information. So my question is, is there another cleaner way to do this?
UPDATE:
Here's the part that creates the input fields in a nested for loop (looping through each task, and then for each task all its timesegments):
<% for (int i = 0; i < Model.Tasks.Count; i++)
{
var task = Model.Tasks[i];
%>
<tr class="taskrow">
<td>
<input type="button" value="Delete" id="<%:i %>" class="deletebutton" />
</td>
<td class="customer">
<%: task.Project.Customer.Name %>
</td>
<td class="project">
<%: task.Project.Name %>
</td>
<td class="task">
<%: task.Name %>
</td>
<% for (int j = 0; j < task.CurrentTimeSegments.Count; j++)
{ %>
<td>
<%: Html.TextBoxFor(model => model.Tasks[i].CurrentTimeSegments[j].TimeSpanHours, new { #class = "hourInput" })%>
<%: Html.ValidationMessageFor(model => model.Tasks[i].CurrentTimeSegments[j].TimeSpanHours)%>
</td>
<% } %>
<td class="hourSum"><%:task.WeekTaskHours %></td>
</tr>
<% } %>
Note that this code is in a partialview if it matters.
you could use the $.data jQuery function to save extra information in your this element of the getDescription method. You need to do that when you create this element. I don't know how you do that and if it is possible for you in your current design.
to save the information it would be:
$(element).data('taskId', taskId);
$(element).data('timeSegmentId', timeSegmentId);
If you give the code where you create this element, I could help you.
Then the getDescription method would be
$('.hourInput').focus(function () {
var taskId = $(this).data('taskId');
vat timeSegmentId = $(this).data('timeSegmentId');
var number = '<%: Model.WeekNumber %>';
var year = '<%: Model.Year %>';
var url = '<%=Url.Action("GetDescription", "Timesheet") %>';
$.get(url, { taskId : taskId, timeSegmentId: timeSegmentId, number: number, year: year }, function (data) {
$('#description').val(data);
});
});
and so in your controller
public ActionResult GetDescription(string taskId, string timeSegmentId, int number, int year)
{
try
{
List<Task> tasks = _repository.GetCurrentTasks(number, year);
var description = tasks[taskId].CurrentTimeSegments[timeSegmentId].Description;
return Content(description);
}
catch (Exception)
{
return Content("");
}
}
EDIT:
According to how you create the input text boxes, I think you can do:
<td>
<%: Html.TextBoxFor(model => model.Tasks[i].CurrentTimeSegments[j].TimeSpanHours, new { #class = "hourInput", id = "uniqueId_" + i + j })%>
<%: Html.ValidationMessageFor(model => model.Tasks[i].CurrentTimeSegments[j].TimeSpanHours)%>
<script type="text/javascript">
$(document).ready(function() {
var selector = '#uniqueId_<%=i %><%=j %>';
$(selector).data('taskId', <%=i %>);
$(selector).data('timeSegmentId', <%=j %>);
});
</script>
</td>
Ok, I've been going at this for several hours and I simply cannot find the solution.
I want to get some data from my user. So first, I use a controller to create a view which receives a Model:
public ViewResult CreateArticle()
{
Article newArticle = new Article();
ImagesUploadModel dataFromUser = new ImagesUploadModel(newArticle);
return View(dataFromUser);
}
Then, I have the view:
<asp:Content ID="Content1" ContentPlaceHolderID="MainContentPlaceHolder" runat="server">
<h2>AddArticle</h2>
<% using (Html.BeginForm("CreateArticle", "Admin", FormMethod.Post, new { enctype = "multipart/form-data" })){ %>
<%= Html.LabelFor(model => model.newArticle.Title)%>
<%= Html.TextBoxFor(model => model.newArticle.Title)%>
<%= Html.LabelFor(model => model.newArticle.ContentText)%>
<%= Html.TextBoxFor(model => model.newArticle.ContentText)%>
<%= Html.LabelFor(model => model.newArticle.CategoryID)%>
<%= Html.TextBoxFor(model => model.newArticle.CategoryID)%>
<p>
Image1: <input type="file" name="file1" id="file1" />
</p>
<p>
Image2: <input type="file" name="file2" id="file2" />
</p>
<div>
<button type="submit" />Create
</div>
<%} %>
</asp:Content>
and finally - the original controller, but this time configured to accept the data:
[HttpPost]
public ActionResult CreateArticle(ImagesUploadModel dataFromUser)
{
if (ModelState.IsValid)
{
HttpPostedFileBase[] imagesArr;
imagesArr = new HttpPostedFileBase[2];
int i = 0;
foreach (string f in Request.Files)
{
HttpPostedFileBase file = Request.Files[f];
if (file.ContentLength > 0)
imagesArr[i] = file;
}
The rest of this controller does not matter since no matter what I do, the count attribute of Request.Files (or Request.Files.Keys) remains 0. I simply can't find a way to pass the files from the form (the Model passes just fine).
You might want to consider not posting the files with the rest of the form- there are good reasons and other ways you can achieve what you want.
Also, check out this question and this advice regarding file uploads in MVC.
You could add the files to your view model:
public class ImagesUploadModel
{
...
public HttpPostedFileBase File1 { get; set; }
public HttpPostedFileBase File2 { get; set; }
}
And then:
[HttpPost]
public ActionResult CreateArticle(ImagesUploadModel dataFromUser)
{
if (ModelState.IsValid)
{
// Use dataFromUser.File1 and dataFromUser.File2 directly here
}
return RedirectToAction("index");
}