Every example that I can find of an MVC4 app has the edit working on one row of data at a time. It displays all the rows of data with each row having an edit which takes you to another page and allows you to edit that one row.
What I would like to do is display all the data elements in rows and instead of having the user have to click EDIT on each row, all the rows' data points would already be in text boxes which the user can directly update. And there is just one SAVE on the page that would just save all the updates/edits at once.
How can I setup my MVC app to support that?
You can use an EditorTemplates for this. The below example shows the normal form posting example. You can ajaxify it if you need by using the serialize method and sending form values.
Assuming You need to Edit the List of Student Names for a course. So Let's create some viewmodels for that
public class Course
{
public int ID { set;get;}
public string CourseName { set;get;}
public List<Student> Students { set;get;}
public Course()
{
Students=new List<Student>();
}
}
public class Student
{
public int ID { set;get;}
public string FirstName { set;get;}
}
Now in your GET action method, you create an object of our view model, initialize the Students collection and send it to our strongly typed view.
public ActionResult StudentList()
{
Course courseVM=new Course();
courseVM.CourseName="Some course from your DB here";
//Hard coded for demo. You may replace this with DB data.
courseVM.Students.Add(new Student { ID=1, FirstName="Jon" });
courseVM.Students.Add(new Student { ID=2, FirstName="Scott" });
return View(courseVM);
}
Now Create a folder called EditorTemplates under Views/YourControllerName. Then create a new view under that called Student.cshtml with below content
#model Student
#{
Layout = null;
}
<tr>
<td>
#Html.HiddenFor(x => x.ID)
#Html.TextBoxFor(x => x.FirstName ) </td>
</tr>
Now in our main view (StudentList.cshtml), Use EditorTemplate HTML helper method to bring this view.
#model Course
<h2>#Model.CourseName</h2>
#using(Html.BeginForm())
{
<table>
#Html.EditorFor(x=>x.Students)
</table>
<input type="submit" id="btnSave" />
}
This will bring all the UI with each of your student name in a text box contained in a table row. Now when the form is posted, MVC model binding will have all text box value in the Students property of our viewmodel.
[HttpPost]
public ActionResult StudentList(Course model)
{
//check for model.Students collection for each student name.
//Save and redirect. (PRG pattern)
}
Ajaxified solution
If you want to Ajaxify this, you can listen for the submit button click, get the form and serialize it and send to the same post action method. Instead of redirecting after saving, you can return some JSON which indicates the status of the operation.
$(function(){
$("#btnSave").click(function(e){
e.preventDefault(); //prevent default form submit behaviour
$.post("#Url.Action("StudentList",YourcontrollerName")",
$(this).closest("form").serialize(),function(response){
//do something with the response from the action method
});
});
});
You just need to specify the right model, list of example, and send the ajax with have information on each row (element of the array), read it on the server side and update each element accordingly. For this goal you use Post request. Just pass the list of elements as a parameters into the controller and pass it using the ajax.
For example you controller could be defined as:
public ActionResult Update(List<MyEntity> list)
{
...
}
public class MyEntity
{
public string Name {get; set;}
public int Count {get; set;}
}
and JavaScript could be as:
var myList = new Array();
// fill the list up or do something with it.
$.ajax(
{
url: "/Update/",
type: "POST",
data: {list: myList}
}
);
And of course your "Save" button has click event handler that will call that functionality with the ajax call.
For your convenience you can consider using KnockoutJS or other MVVM frameworks to bind the data with the DOM on the client side.
Related
I have a view model that is used to display a form on one view, and then is also used to represent the POST data to an action. The action then displays another view model that contains much of the same data from the first view model. However, the first view model has several "display only" properties that are also required on the second view model (for display only on the second view also).
I am wondering what the best way to pass this "display only" data to the second view would be. Currently, the best solution I have come up with is to have a bunch of hidden form fields that contain the display only property values, and then the model gets auto-populated for the action that handles the form POST. However, using hidden form fields seems very "hackish", and there seems like there should be a better solution to passing this data to another view The action doesn't need the display only information, it is only accessing it to populate the properties of the second view model that is passed to the second view.
Let me just explain my question with code, as what I am after is probably better understood through code than words.
Models:
public class SearchFilters
{
// ...
}
public class SearchResult
{
public int Id { get; set; }
public bool Selected { get; set; }
public string SomeDisplayValue1 { get; set; }
public string SomeDisplayValue2 { get; set; }
// ...
}
public class ResultsViewModel
{
public IList<SearchResult> Results { get; set; }
// ...
}
public class DoSomethingWithSelectedResultsViewModel
{
public IList<SearchResult> SelectedResults { get; set; }
public string SomeOtherProperty { get; set; }
// ...
}
Controller:
[HttpPost]
public ActionResult Results(SearchFilters filters)
{
ResultsViewModel results = new ResultsViewModel();
// ...
return new View(results);
}
[HttpPost]
public ActionResult DoSomethingWithSelectedResults(ResultsViewModel model)
{
// ...
return View(new DoSomethingWithSelectedResultsViewModel
{
SelectedResults = model.Results.Where(r => r.Selected).ToList(),
SomeOtherProperty = "...",
// ...
});
}
View: Results.cshtml
#model ResultsViewModel
#using (Html.BeginForm("DoSomethingWithSelectedResults", "Search"))
{
<table>
for (int i = 0; i < Model.Results.Count; i++)
{
<tr>
<td>
#Html.CheckBoxFor(m => Model.Results[i].Selected)
#* I would like to eliminate these hidden inputs *#
#Html.HiddenFor(m => Model.Results[i].Id)
#Html.HiddenFor(m => Model.Results[i].SomeDisplayValue1)
#Html.HiddenFor(m => Model.Results[i].SomeDisplayValue2)
</td>
<td>#Html.DisplayFor(m => Model.Results[i].SomeDisplayValue1)</td>
<td>#Html.DisplayFor(m => Model.Results[i].SomeDisplayValue2)</td>
<tr>
}
</table>
<button type="submit">Do Something With Selected Results</button>
}
As far as I know, one of the best way to pass data from View to another View through a Controller is to use ViewBag, ViewData or TempData. As an example, you can pass the data retrieved from View I as shown below:
TempData[DataToBePassed] = model.CustomData;
And then retrieve this data in View II similar to that:
#if(TempData[DataToBePassed] != null)
{
var dataFromFirstView = TempData[DataToBePassed];
}
For more information take a look at When to use ViewBag, ViewData, or TempData in ASP.NET MVC 3 applications.
You could put the model in the TempData property of the controller, that way it's automatically available in the next request.
More here
Found what I was looking for, I just hadn't worked with MVC enough yet to know about it. The Controller.UpdateModel method does exactly what I was looking for.
Example (using the code from the question):
[HttpPost]
public ActionResult DoSomethingWithSelectedResults()
{
// Load initial model data here, in this case I had simply cached the results in
// temp data in the previous action as suggested by Emeka Awagu.
ResultsViewModel model = (ResultsViewModel)TempData["results"];
// Call UpdateModel and let it do it's magic.
UpdateModel(model);
// ...
return View(new DoSomethingWithSelectedResultsViewModel
{
SelectedResults = model.Results.Where(r => r.Selected).ToList(),
SomeOtherProperty = "...",
// ...
});
}
Using this method I was able to eliminate all the hidden form fields and did not have to write any custom copy logic, since UpdateModel deals with it automatically.
Note: I did have to implement some custom model binders to get things to work correctly with dictionaries and collections (see here, here, and here).
I have main view and main model.
Inside this view i have such lines
foreach (var loadTask in Model.LoadTasks)
{
Html.RenderPartial("TripUpdateTask", new TripUpdateTaskModel { Task = loadTask });
}
So in main model i have the
public List<OrderTaskRecord> LoadTasks { get; set; }
Submodel is:
public class TripUpdateTaskModel
{
public OrderTaskRecord Task { get; set; }
}
I played and played but still unable to save the data. Here is current and simple look.
<tr>
<td>Actual Time:</td>
<td>#Html.TextBoxFor(m => m.Task.Task.ActualTime)</td>
</tr>
In raw html these time controls have same name and same id. So i dont know what need to do to save that.
I mean the data entered on main view back to controller fine, but not from partial view
I use Html.BeginForm and fieldset in main view like this:
#using (Html.BeginForm("TripUpdate", "SupplierBookingUpdate", FormMethod.Post, new { id = "SupplierBookingUpdateSave" }))
{
<fieldset>
.. table
</fieldset>
}
Rather than trying to name each text box individually, I would recommend to bind your collection of OrderTaskRecord. That way you do not need to worry about a number of text boxes and their names.
There's a good introduction from Scott Hanselman on the subject.
You can also try another example here: http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
If you want to pass a custom name to the input being generated use this. It passes the html attributes to the TextBoxFor method:
#Html.TextBoxFor(m => m.Task.Task.ActualTime, new { Name = "yourName" )
Of course, you can create counters, etc in here to keep them unique.
You can extract them later in your Controller by getting the Form data:
Request.Form["yourName"];
Suppose I have a simple model:
public class studentNames
{
string name{get; set;}
}
Now, if I scaffold it in view by create mode, only one model would be created. I want to create multiple objects in a single view.
Something like:
<form action="someAction">
Name of student1: <student1 name input box>
Name of student2: <student2 name input box>
<save button>
</form>
When this button would be clicked, a List would be returned in the controller, where I would be able to save them in database.
How can I achieve this?
Thanks in advance.
You could start by reading the convention that the model binder expects for binding to lists. And then name your input fields appropriately:
#using (Html.BeginForm("SomeAction", "SomeController"))
{
#for (var i = 0; i < 5; i++)
{
<div>
Name of student: #Html.TextBox("name[" + i + "]", null)
</div>
}
<button type="submit">Save</button>
}
and then your POST controller action could take a collection of students:
[HttpPost]
public ActionResult SomeAction(IList<StudentNames> students)
{
...
}
If you want to be able to dynamically add and remove items in this collection inside the view I would recommend you reading the Editing a variable length list article from Steven Sanderson where he illustrates a nice technique that would allow you to build such interface.
I was wondering what the best way to approach this problem in ASP.NET MVC would be. The following is a trivial example of what I'd like to be able to do:
I have a webpage with textbox and a submit button. When the submit button is pressed the I would like the contents to be displayed on the same webpage. When it is pressed again I would like what was already displayed from the first submission to be displayed as well as the new data that was just submitted.
I have tried saving this data to a model, but the model is wiped clean every time the form posts. How could I do this and keep the data from the post before the last one (and the post before that)?
If you want data to persist between requests, as a starting point I would use 'TempData'. The TempData property value is stored in session state and exists until it is read or until the Session expires.
Example ViewModel:
public class SomeClass
{
public string Something { get; set; }
public List<string> RetainedValues { get; set; }
}
Example Controller:
[HttpGet]
public ActionResult Index()
{
return View("Index");
}
[HttpPost]
public ActionResult Index(SomeClass postedValues)
{
// retrieve retained values
var retained = (List<string>) TempData["RetainedValues"] ?? new List<string>();
retained.Add(postedValues.Something);
// save for next post
TempData["RetainedValues"] = retained;
// setup viewmodel
var model = new SomeClass
{
RetainedValues = retained
};
return View("Index", model);
}
Example View (strongly typed):
<div>
#foreach(var item in Model.RetainedValues)
{
<div>#item</div>
}
</div>
#using(Html.BeginForm())
{
#Html.EditorFor(m=>m.Something)
<input type="submit"/>
}
Just put an hidden field for your model property then your previews value will be loaded on it and passed it back to the next post.
Ex.: #Html.HiddenFor(model => model.YourProperty)
So knowing that you could have two properties ex.: one named newValue and other called allValues.
the allValues you use it with an hidden field and your newValue you use to insert the new ones. So on post you just add the newValue to the allValues.
Something like that:
model.allValues += newValue;
--UPDATE
Or you can use session or tempdata as mentioned by #Jesse
For this case I would prefer to use hidden fields as it has a lower complexity and its data didnt need be secure as it will be shown to the user anyway.
So I am new to ASP.NET MVC and I would like to create a view with a text box for each item in a collection. How do I do this, and how do I capture the information when it POSTs back? I have used forms and form elements to build static forms for a model, but never dynamically generated form elements based on a variable size collection.
I want to do something like this in mvc 3:
#foreach (Guest guest in Model.Guests)
{
<div>
First Name:<br />
#Html.TextBoxFor(???) #* I can't do x => x.FirstName here because
the model is of custom type Invite, and the
lambda wants to expose properties for that
type, and not the Guest in the foreach loop. *#
</div>
}
How do I do a text box for each guest? And how do I capture them in the action method that it posts back to?
Thanks for any help.
Definitely a job for an editor template. So in your view you put this single line:
#Html.EditorFor(x => x.Guests)
and inside the corresponding editor template (~/Views/Shared/EditorTemplates/Guest.cshtml)
#model AppName.Models.Guest
<div>
First Name:<br />
#Html.TextBoxFor(x => x.FirstName)
</div>
And that's about all.
Now the following actions will work out of the box:
public ActionResult Index(int id)
{
SomeViewModel model = ...
return View(model);
}
[HttpPost]
public ActionResult Index(SomeViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
}
// TODO: do something with the model your got from the view
return RedirectToAction("Success");
}
Note that the name of the editor template is important. If the property in your view model is:
public IEnumerable<Guest> Guests { get; set; }
the editor template should be called Guest.cshtml. It will automatically be invoked for each element of the Guests collection and it will take care of properly generating ids and names of your inputs so that when you POST back everything works automatically.
Conclusion: everytime you write a loop (for or foreach) inside an ASP.NET MVC view you should know that you are doing it wrong and that there is a better way.
You can do this:
#for (int i = 0; i < Model.Guests.Count; i++) {
#Html.TextBoxFor(m => m.Guests.ToList()[i].FirstName)
}
There are more examples and details on this post by Haacked.
UPDATE: The controller post action should look like this:
[HttpPost]
public ActionResult Index(Room room)
{
return View();
}
In this example I'm considering that you have a Room class like this:
public class Room
{
public List<Guest> Guests { get; set; }
}
That's all, on the post action, you should have the Guests list correctly populated.