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>
Related
I have numerous data displayed in a table, let's say a long list of users (first name & last name), so I set up a paging feature to display the elements by pages via the PagedList NuGet package. I was inspired by this tutorial: https://learn.microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/sorting-filtering-and-paging-with-the-entity-framework-in-an-asp-net-mvc-application
I implemented a drop-down list in my view, so that I can directly choose the number of elements to display per page. I managed to include a jQuery script that makes the page size update whenever the drop-down list has a new selected value.
Using the mentioned tutorial , I also added a search feature: submitting a string in a search form allows to filter the data.
My problem is: changing the page size by selecting a new value in the drop-down list after having done a search doesn't work: the search results are reset, all the entries being displayed instead. I guess I forgot to pass some parameter somewhere but I just can't figure out where...
Here is my controller:
public ActionResult Index(string sortOrder, string currentFilter, string searchString, int? page, int? PageSize)
// Sort order is passed to view in order to keep it intact while clicking in another page link
ViewBag.CurrentSort = sortOrder;
// Ascending or descending sorting by first or last name according to sortOrder value
ViewBag.LastNameSortParm = String.IsNullOrEmpty(sortOrder) ? "lastname_desc" : "";
ViewBag.FirstNameSortParm = sortOrder == "firstname" ? "firstname_desc" : "firstname";
// Not sure here
if (searchString == null)
{
searchString = currentFilter;
}
// Pass filtering string to view in order to maintain filtering when paging
ViewBag.CurrentFilter = searchString;
var users = from u in _db.USER select u;
// FILTERING
if (!String.IsNullOrEmpty(searchString))
{
users = users.Where(u => u.lastname.Contains(searchString)
|| u.firstname.Contains(searchString)
}
// Ascending or descending filtering by first/last name
switch (sortOrder)
{
case "lastname": // Ascending last name
users = users.OrderBy(u => u.lastname);
break;
case "lastname_desc": // Descending last name
users = users.OrderByDescending(u => u.lastname);
break;
case "firstname": // Ascending first name
users = users.OrderBy(u => u.firstname);
break;
case "firstname_desc": // Descending first name
users = users.OrderByDescending(u => u.firstname);
break;
default:
users = users.OrderBy(u => u.lastname);
break;
}
// DROPDOWNLIST FOR UPDATING PAGE SIZE
int count = _db.USER.OrderBy(e => e.Id).Count(); // Total number of elements
// Populate DropDownList
ViewBag.PageSize = new List<SelectListItem>() {
new SelectListItem { Text = "10", Value = "10", Selected = true },
new SelectListItem { Text = "25", Value = "25" },
new SelectListItem { Text = "50", Value = "50" },
new SelectListItem { Text = "100", Value = "100" },
new SelectListItem { Text = "All", Value = count.ToString() }
};
int pageNumber = (page ?? 1);
int pageSize = (PageSize ?? 10);
ViewBag.psize = pageSize;
return View(users.ToPagedList(pageNumber, pageSize));
}
And my Index.cshtml view:
<script src="~/Scripts/jquery-3.2.1.js" type="text/javascript"></script>
<script type="text/javascript">
$(function () { // Submit pageSizeForm when another pageSize value is selected
$("#pageSize").change(function () {
$("#pageSizeForm").submit();
});
});
</script>
#model PagedList.IPagedList<AfpaSIPAdmin.Models.USER>
#using PagedList.Mvc;
<link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
#{
ViewBag.Title = "Users management";
}
<h1>Users management</h1>
<!-- Creating a new entry in table -->
<p>
#Html.ActionLink("Create new user", "Create")
</p>
<!-- Filtering table entries -->
#using (Html.BeginForm("Index", "Users", FormMethod.Get, new { id = "filterForm" }))
{
<p>
Filter: #Html.TextBox("SearchString", ViewBag.CurrentFilter as string, new { #placeholder = "First or last name..." })
<input type="submit" value="Apply"/>
</p>
}
<!-- Display table -->
<table class="table">
<tr>
<th>
#Html.ActionLink("Last name", "Index", new {
sortOrder = ViewBag.LastNameSortParm,
currentFilter = ViewBag.CurrentFilter
})
</th>
<th>
#Html.ActionLink("First name", "Index", new {
sortOrder = ViewBag.FirstNameSortParm,
currentFilter = ViewBag.CurrentFilter
})
</th>
<th style="min-width: 170px"></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td style = "min-width: 150px">
#Html.DisplayFor(modelItem => item.lastname)
</td>
<td style = "min-width: 150px">
#Html.DisplayFor(modelItem => item.firstname)
</td>
<td> <!-- Using images as buttons for actions -->
<a href="#Url.Action("Edit", "Users", new { id = item.Id })" title="Edit">
<img src="~/Content/images/edit.gif" />
</a>
<a href="#Url.Action("Details", "Users", new { id = item.Id })" title="Details">
<img src="~/Content/images/info.gif" />
</a>
<a href="#Url.Action("Delete", "Users", new { id = item.Id })" title="Delete">
<img src="~/Content/images/delete.gif" />
</a>
</td>
</tr>
}
</table>
<br/>
<!-- Paging -->
#using (Html.BeginForm("Index", "Users", FormMethod.Get, new { id = "pageSizeForm" }))
{
<div class="pager">
Page #(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) sur #Model.PageCount<br/>
#Model.Count of #Model.TotalItemCount elements
#Html.PagedListPager(Model, page => Url.Action("Index", new {
page,
sortOrder = ViewBag.CurrentSort,
currentFilter = ViewBag.CurrentFilter,
searchString = ViewBag.CurrentFilter,
pageSize = ViewBag.psize
}))
<!-- DropDownList for setting page size -->
Elements per page :
#Html.DropDownList("pageSize")
</div>
}
The reason is because you have 2 forms. When you submit the first form containing the textbox, the only value you send back to the controller is SearchString and all the other parameters in your method will be their default (for example when you return the view, PageSize will default to null and therefore return only 10 records even if the user previously selected say 50.
Likewise, when you submit the 2nd form containing dropdownlist for the page size, the value of SearchString will be null because its not sent in the request.
You need to have one form only containing both form controls. And if you wanted to send additional properties, for example the current sort order, then you can add those as query string values in the form element (for example, #using(Html.BeginForm("Index", "Users", new { sortOrder = .... }, FormMethod.Get))
I would also strongly recommend you use a view model containing the properties you need in the view and strongly bind to them rather than using ViewBag
public class UsersVM
{
public string SearchString { get; set; }
public int PageSize { get; set; }
public IEnumerable<SelectListItem> PageSizeOptions { get; set; }
.....
public IPagedList<USER> Users { get; set; }
}
View
#model UsersVM
...
#using(Html.BeginForm("Index", "Users", FormMethod.Get))
{
#Html.LabelFor(m => m.SearchString)
#Html.TextBoxFor(m => m.SearchString)
#Html.LabelFor(m => m.PageSize)
#Html.DropDownListFor(m => m.PageSize, Model.PageSizeOptions)
<input type="submit" value="Filter" />
}
....
<div class="pager">
Page #(Model.Users.PageCount < Model.Users.PageNumber ? 0 : Model.Users.PageNumber)
....
#Html.PagedListPager(Model.Users, page => Url.Action("Index", new {
page,
sortOrder = Model.CurrentSort,
currentFilter = Model.CurrentFilter,
searchString = Model.CurrentFilter,
pageSize = Model.PageSize
}))
</div>
and in the controller method, initialize a new instance of UsersVM and assign its properties
public ActionResult Index(string sortOrder, string currentFilter, string searchString, int? page, int? pageSize)
{
UsersVM model = new UsersVM();
....
var users = from u in _db.USER select u;
....
pageSize = pageSize ?? 10;
model.PageSize = pageSize.Value;
model.Users = users.ToPagedList(pageNumber, pageSize);
model.PageSizeOptions = new List<SelectListItem> { .... };
return View(model);
}
I have a list in asp.net mvc 5
I have limited the number of records to be displayed in page.
now on scroll I do ajax call, the first call works fine but when I scroll down more it recall the function repeatedly 5 to 10 times and lost it again display the data, it was really strange, I could not find solution
My Controller:
public ActionResult Index()
{
int starting = 0;
if (Request.Form["starting"] != null)
{
starting = Convert.ToInt32(Request.Form["starting"]);
}
int takes = 15;
if (Request.Form["takes"] != null)
{
takes = Convert.ToInt32(Request.Form["takes"]);
}
//string strpost = "&ajax=1";
var query = db.MyEmployee.ToList().Skip(starting).Take(takes);
if (Request.IsAjaxRequest())
{
starting = starting+15;
query = db.MyEmployee.ToList().Skip(starting).Take(takes);
ViewData["starting"] = starting;
ViewBag.takes = takes;
return PartialView("_PartialIndex",query);
}
ViewBag.starting = starting;
ViewBag.takes = takes;
return View(query);
}
My Model:
public class Employee
{
public int Id { get; set; }
public string FullName { get; set; }
public string Email { get; set; }
}
My View and partial view code:
<div id="mypage">
#model IEnumerable<MVC5WAuth.Models.Employee>
#{
ViewBag.Title = "Index";
}
<h2>Index 1</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Id)
</th>
<th>
#Html.DisplayNameFor(model => model.FullName)
</th>
<th>
#Html.DisplayNameFor(model => model.Email)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Id)
</td>
<td>
#Html.DisplayFor(modelItem => item.FullName)
</td>
<td>
#Html.DisplayFor(modelItem => item.Email)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }) |
#Html.ActionLink("Details", "Details", new { id = item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.Id })
</td>
</tr>
}
</table>
<script type="text/javascript">
$(window).scroll(function () {
if ($(window).scrollTop() == $(document).height() - $(window).height()) {
// ajax call get data from server and append to the div
var ajax_image = "<img src='../Content/loading.GIF' >";
$('#mypage').html(ajax_image);
var params = '&starting=' + #ViewBag.starting + '&takes=' + #ViewBag.takes;
$.ajax({
url:'#Url.Action("Index", "Employees")',
type: "POST",
data: params,
})
.done(function (r) {
$('#mypage').html(r);
});
}
});
</script>
You current code is just replacing the existing view each time you scroll and make a ajax call, not updating the existing view with the next set of rows you want. You code also has some inefficiencies such as materializing all your records to an in-memory set before calling .Skip() and .Take().
You need to break this into 2 separate methods and views, one to generate the initial view, and one to return a partial of just the records you want to append to the main view.
Controller
public ActionResult Index()
{
return View();
}
public ActionResult Fetch(int startIndex)
{
query = db.MyEmployee.OrderBy(x => x.ID).Skip(startIndex).Take(15);
return PartialView(query);
}
Index.cshtml
#model IEnumerable<MVC5WAuth.Models.Employee>
....
<table class="table">
<thead>
<tr>
<th>#Html.DisplayNameFor(model => model.Id)</th>
....
</tr>
</thead>
<tbody id="tbody">
#{ Html.RenderAction("Fetch", new { startIndex = 0 }); } // generate the 1st 15 rows
</tbody>
</table>
<script type="text/javascript">
var start = 15;
var url = '#Url.Action("Fetch")';
var tbody = $('#tbody');
$(window).scroll(function () {
if ($(window).scrollTop() == $(document).height() - $(window).height()) {
....
$.get(url, { startIndex: start }, function(response) {
tbody.append(response);
start += 15; // increment for next call
});
}
});
</script>
Fetch.cshtml
#model IEnumerable<MVC5WAuth.Models.Employee>
#foreach (var item in Model)
{
<tr>
<td>#Html.DisplayFor(m => item.Id)</td>
....
</tr>
}
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 working on a MVC3 ASP.Net application. I'm trying to figure out how to set the Quantity variable so when I pass it through to the controller with Html.ActionLink it has the correct number. Here's the view's code
#model IEnumerable<GreatVideosTrainingApplication.Models.Candy>
#{
ViewBag.Title = "Great Videos";
List<GreatVideosTrainingApplication.Models.Candy> candies = new List<GreatVideosTrainingApplication.Models.Candy>();
foreach (var candy in Model)
{
candies.Add(candy);
}
var grid = new WebGrid(candies);
var Quantity = 0;
}
<p>Welcome To Great Videos! The best source for your favorite DVDs and Blu-Rays</p>
<img src ="/Content/Images/dvd50.jpg" />
<p></p>
<img src="/Content/Images/bluray.jpg" />
<form method="post" action="/ShoppingCart/AddToCandyCart/"+item.CandyID >
#grid.GetHtml(#columns: grid.Columns(
grid.Column("Name"),
grid.Column("Price"),
grid.Column("Quantity", format: (item) => #Html.TextBox("Quantity", #Quantity)),
grid.Column("AddToCart", format: (item) => Html.ActionLink("Add To Cart", "AddToCandyCart", "ShoppingCart", new { id = item.CandyID, quantity = #Quantity }, ""))
)
)
</form>
I'm trying to set the value for the quantity with the Html.TextBox but it's not working. Keep in mind here I don't know javascript, and I'm extremely new to MVC3. Any and all help is greatly appreciated though.
public ActionResult AddToCandyCart(int id, FormCollection values)
{
// Add it to the shopping cart
var quantity = values["Quantity"];
var cart = ShoppingCart.GetCart(this.HttpContext);
// Retrieve the video from the database
var addedCandy = storeDB.Candies.Single(Candy => Candy.CandyID == id);
cart.AddToCandyCart(addedCandy, int.Parse(quantity));
// Go back to the main store page for more shopping
return RedirectToAction("Index");
}
The following code worked for me when including a textbox. I had trouble with the html helpers so I just wrote the code for the input box directly. I hope this helps.
grid.Column("Quantity", format: #<text><input name="Quantity" type="text" value="#item.Quantity"</text>))
Figured out the problem from mixing a variety of sources. Wanted to thank everyone. Here's the view.
#model IEnumerable<GreatVideosTrainingApplication.Models.Candy>
#{
ViewBag.Title = "Great Videos";
List<GreatVideosTrainingApplication.Models.Candy> candies = new List<GreatVideosTrainingApplication.Models.Candy>();
foreach (var candy in Model)
{
candies.Add(candy);
}
var grid = new WebGrid(candies);
var Quantity = 0;
}
<p>Welcome To Great Videos! The best source for your favorite DVDs and Blu-Rays</p>
<img src ="/Content/Images/dvd50.jpg" />
<p></p>
<img src="/Content/Images/bluray.jpg" />
<form method="post" action="../ShoppingCart/AddToCandyCart/" >
#using (Html.BeginForm()) {
#grid.GetHtml(#columns: grid.Columns(
grid.Column("Name"),
grid.Column("Price"),
grid.Column("Quantity", format: #<text><input name="Quantity" type="text" value="#Quantity"</text>),
grid.Column("AddToCart", format: #<text><input type="submit" value="Add To Cart" name="submit" /></text>)
)
)
}
</form>
Here's the Action Controller
[HttpPost]
public ActionResult AddToCandyCart(FormCollection values)
{
int id = 1;
string[] quantities = values["Quantity"].Split(',');
foreach (var item in quantities)
{
try
{
int quantity = int.Parse(item);
if (quantity >= 1)
{
// Add the candy to the shopping cart
var addedCandy = storeDB.Candies.Single(Candy => Candy.CandyID == id);
var cart = ShoppingCart.GetCart(this.HttpContext);
cart.AddToCandyCart(addedCandy, int.Parse(item));
}
}
catch (Exception e)
{
return View("Failed");
}
id++;
}
// Go back to the main store page for more shopping
return RedirectToAction("Index");
}
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.