For some input fields I need to replace the standard dropdown-list of mvc through a filterable and sortable table since there are several columns and a lot of entries to select from. I added a partial view but now I have trouble with creating the table manually by combining jquery and Razor (displaying the Model data, and then sorting and filtering as well).
Does anybody know a good sample where this is done?
Finally I managed to create a sample. So if somebody else is looking for some help with this requirement - I hope you find it useful.
What is needed: When filling in a form the user should be able to select an entry from a list of cars. The selection is presented in a modal dialog with a table. When the user clicks on an entry it will be written in the field on the form.
I am using MVC 5.2 and Bootstrap 3.
My test class Car (Model):
public class Car {
public Car(long id, string license_plate, string car_type) {
this.id = id;
this.license_plate = license_plate;
this.car_type = car_type;
}
public long id { set; get; }
public string license_plate {set; get; }
public string car_type { set; get; }
}
Here the controller:
public class HomeController : Controller {
static List<Car> carList = new List<Car>();
public ActionResult Index() {
if (carList.Count() == 0 )
carList = createCarList();
return View(new Car(99,"",""));
}
// call the modal dialog
public ActionResult CarSelection() {
return PartialView("_CarSelection", carList);
}
[HttpPost]
// called by the modal dialog to pass the value the user selected
public ActionResult CarSelected(int carId) {
Car theCar = carList.ElementAt(carId);
return View("Index", theCar);
}
// some data entries
private List<Car> createCarList () {
carList.Add(new Car(0, "", "Fork Lift"));
carList.Add(new Car(1, "B-AX 54", "Fordson Power Major"));
carList.Add(new Car(2, "B-RX 837", "Unimog"));
carList.Add(new Car(3, "", "Sidelift Crane"));
carList.Add(new Car(4, "", "Bobcat Excavator"));
carList.Add(new Car(5, "K-O 38", "Cat Track Loader"));
carList.Add(new Car(6, "CW-X 2734", "Snowplow"));
carList.Add(new Car(7, "KI-MR 74", "Toyota Pickup"));
return carList;
}
}
In the View is an input field and a button to call the modal dialog:
#model ModalBootstrap.Models.Car
<form class="form-horizontal">
<div class="form-group">
#Html.Label("Car", htmlAttributes: new { #class = "control-label col-sm-2" })
<div class="col-sm-4">
#Html.EditorFor(model => model.car_type, new { htmlAttributes = new { #class = "form-control" } })
</div>
<div class="col-sm-6">
#Html.ActionLink("?", "CarSelection", "Home", null, new { #class = "modal-link btn btn-success" })
</div>
</div>
</form>
The container for the Bootstrap modal is in _Layout.cshtml:
<div id="modal-container" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content" style="width:70%; margin:20px auto !important;">
</div>
</div>
</div>
<script type="text/javascript">
$(function () {
// Initalize modal dialog
// attach modal-container bootstrap attributes to links with .modal-link class.
// when a link is clicked with these attributes, bootstrap will display the href content in a modal dialog.
$('body').on('click', '.modal-link', function (e) {
e.preventDefault();
$(this).attr('data-target', '#modal-container');
$(this).attr('data-toggle', 'modal');
});
// Attach listener to .modal-close-btn's so that when the button is pressed the modal dialog disappears
$('body').on('click', '.modal-close-btn', function () {
$('#modal-container').modal('hide');
});
//clear modal cache, so that new content can be loaded
$('#modal-container').on('hidden.bs.modal', function () {
$(this).removeData('bs.modal');
});
});
</script>
And finally the Partial View _CarSelection.cshtml which handles the modal dialog with the data entries. To allow filtering and sorting I use the plugin DataTables:
#model IEnumerable<ModalBootstrap.Models.Car>
<div class="modal-header">
<button id="modalButton" type="button" class="close" data-dismiss="modal">×</button>
<h4 class="modal-title">Please Select a Car</h4>
</div>
<div class="modal-body">
<table id="carsTab" class="table table-striped table-hover" style="width:100%;">
<thead>
<tr>
<th>Id</th>
<th>License Plate</th>
<th>Car Type</th>
</tr>
</thead>
<tbody>
#foreach ( var item in Model ) {
<tr>
<td>#item.id</td>
<td>#item.license_plate</td>
<td>#item.car_type</td>
</tr>}
</tbody>
</table>
</div>
<form name="carForm" action="/Home/CarSelected" method="post" id="carForm"></form>
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/DataTables/jquery.dataTables.min.js"></script>
<script src="~/Scripts/DataTables/dataTables.bootstrap.min.js"></script>
<link href="~/Content/DataTables/css/jquery.dataTables.min.css" rel="stylesheet" />
<script>
$(document).ready(function () {
$('#carsTab').dataTable({
"order": [[ 1, "asc"]],
"paging": false,
"ordering": true,
"scrollY": "450px", // table size
"scrollCollapse": false,
"columnDefs": [ {
"targets": [ 0 ], // hide id column
"visible": false,
"searchable": false
}]
});
// alignment of table headers
$('#modal-container').on('shown.bs.modal', function (e) {
$.fn.dataTable.tables({ visible: true, api: true }).columns.adjust();
});
// if user clicks on a row trigger a submit. /Home/CarSelected is called (see <form>)
$('#carsTab tbody').on('click', 'tr', function () {
var data = $('#carsTab').DataTable().row(this).data();
var para = $("<input>").attr("type","hidden").attr("id","carId").attr("name","carId").val(data[0]);
$('#carForm').append($(para));
$("#carForm").submit();
});
});
</script>
Related
I have the following problem:
A collection of "Gifts" gets passed as a parameter to a view.
Each item inside this collection is a object from the Model class "Gift".
There are only two properties inside this class: name and price.
I put the class inside the controller for the sake of simplicity.
This is the action method:
// GET: Order/CreateGift
[HttpGet]
public ActionResult CreateGift()
{
var initialData = new[]
{
new Gift{ Name = "Tricycle", Price = 69.95 },
new Gift{ Name = "PS4 game", Price = 29.99 },
new Gift{ Name = "Lazergun", Price = 49.99}
};
return View(initialData);
}
On the view I can dynamically add new items to the collection, thanks to the BeginCollectionItem Html helper.
This is my View:
#model IEnumerable<DemoWebShop.Controllers.Gift>
<script src="~/Scripts/jquery-3.4.1.min.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script type="text/javascript" src="http://ajax.cdnjs.com/ajax/libs/json2/20110223/json2.js"></script>
<script type="text/javascript">
$(document).ready(function () {
$("#addItem").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) { $("#editorRows").append(html); }
});
return false;
});
}); // end document.ready function
</script>
<h2>Gift List</h2>
What do you want for your birthday?
#using (Html.BeginForm())
{
<div class="form-horizontal">
<div id="editorRows">
#foreach (var item in Model)
{
Html.RenderPartial("GiftEditorRow", item);
}
</div>
#Html.ActionLink("Add another...", "BlankEditorRow", null, new { id = "addItem" })
<input type="submit" value="Finished" />
#*<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Aanmaken" class="btn btn-default" />
</div>
</div>*#
</div>
}
This is the partial view called "GiftEditorRow" :
#using HtmlHelpers.BeginCollectionItem
#model DemoWebShop.Controllers.Gift
#{
Layout = null;
}
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="editorRow">
#using (Html.BeginCollectionItem("gifts"))
{
<label>Name: </label>
#Html.TextBoxFor(x => x.Name);
<label>Price: </label>
#Html.TextBoxFor(x => x.Price, new { size = 4 });
<br />
}
</div>
}
Inside my controller I also have this actionmethod:
public ViewResult BlankEditorRow()
{
return View("GiftEditorRow", new Gift());
}
Only the First item that already existed inside the collection, gets passed to the HTTP Post method in my controller.
I found my mistake.
The partial view "GiftEditorRow" still contains the following:
#using (Html.BeginForm()){.....}
If you remove this, it should work.
This is my partial view now:
#using (Html.BeginCollectionItem("gifts"))
{
<label>Name: </label>
#Html.TextBoxFor(Model => Model.Name);
<label>Price: </label>
#Html.TextBoxFor(Model => Model.Price, new { size = 4 });
<br />
}
Now I get all items instead of just the first one.
I am new on MVC. How can I combine two action result on my below code:
public ActionResult TreeView()
{
var db = new PCNMSContext();
return View(db.AssetRegisters.Where(x => !x.ParentAssetID.HasValue).ToList());
}
public ActionResult AssetRegisterIndex(string SearchString)
{
var assetregisters = from m in db.AssetRegisters
select m;
if (!String.IsNullOrEmpty(SearchString))
{
assetregisters = assetregisters.Where(s => s.AssetNumber.Contains(SearchString));
}
return View(assetregisters);
}
The ActionResult above is .. 1st, to generate the list on the TreeView and 2nd is to generate the list on Table with Search Function.
My Model:
public class AssetRegister
{
public int AssetID {get;set;}
public string AssetNumber {get;set}
public string AssetDescription {get;set;}
public int? ParentAssetID {get;set;}
[ForeignKey("ParentAssetID")]
public virtual AssetRegister Parent {get;set;}
public virtual ICollection<AssetRegister> Childs {get;set;}
}
My View:
<div class="row">
<div class="col-lg-4">
<div id="jstree">
#(Html.TreeView(Model)
.EmptyContent("root")
.Children(m => m.Childs)
.HtmlAttributes(new { id = "tree" })
.ChildrenHtmlAttributes(new { #class = "subItem" })
.ItemText(m => m.AssetNumber)
.ItemTemplate(
#<text>
#item.AssetNumber
</text>)
)
</div>
</div>
#using (Html.BeginForm())
{
<div class="col-lg-1">
#Html.TextBox("SearchString")
<button class="btn" type="submit" value="Search">View</button>
</div>
}
<div class="col-lg-7">
<table> I didnt copy all, but this table contains a column of 'AssetRegister' model. There are 4 columns. AssetID,ParentID, AssetNumber,AssetDescription
</table>
</div>
</div>
My script for jsTree:
<script>
$(function () {
var selectedData;
$('#jstree').jstree({
"core": {
"multiple": true,
"check_callback": true,
'themes': {
"responsive": true,
'variant': 'small',
'stripes': false,
'dots': true
}
},
"types": {
"default": {
"icon": "glyphicon glyphicon-record"
},
"root": {
"icon": "glyphicon glyphicon-ok"
}
},
"plugins": ["dnd", "state", "types", "sort"]
}).on('changed.jstree', function (e, data) {
var i, j, r = [];
for (i = 0, j = data.selected.length; i < j; i++) {
r.push(data.instance.get_node(data.selected[i]).text);
}
$('#SearchString').val(r.join(', '));
});
</script>
So when I click the node, the text of the node will appear on #Html.TextBox.
The problem is, to load the data on table and to load the data on treeview is different.
1. How can I combine those ActionResult?
2. When text from node received by the textbox, The submit button should not be clicked, but it should be click automatically. How can I do that?
Thank you.
Why not having a ViewModel containing those you want to return in View?
return View(new MyModel{List1 = list1,List2 = list2});
The best solution for this type of requirement is using ajax to populate the data and then for filtering purpose.
You are submitting the form for searching purpose which is not a good practice.
Check my solution if it could help you.
Step I:
Change your Action Method and Return JsonResult instead of ViewResult:
public ActionResult AssetRegisterIndex(string SearchString)
{
var assetregisters = from m in db.AssetRegisters
select m;
if (!String.IsNullOrEmpty(SearchString))
{
assetregisters = assetregisters.Where(s => s.AssetNumber.Contains(SearchString));
}
return Json(assetregisters,JsonRequestBehavior.AllowGet);
}
Step II:
i. Remove #html.BeginForm
ii. Change type of button to 'button'.
iii.Create html for table.
<div class="col-lg-1">
#Html.TextBox("SearchString")
<button class="btn" type="button" value="Search" id="btnsearch">View</button>
</div>
<div class="row">
<div class="col-lg-4">
<div id="jstree">
#(Html.TreeView(Model)
.EmptyContent("root")
.Children(m => m.Childs)
.HtmlAttributes(new { id = "tree" })
.ChildrenHtmlAttributes(new { #class = "subItem" })
.ItemText(m => m.AssetNumber)
.ItemTemplate(
#<text>
#item.AssetNumber
</text>)
)
</div>
</div>
<div class="col-lg-1">
#Html.TextBox("SearchString")
<button class="btn" type="button" value="Search" id="btnsearch">View</button>
</div>
<div class="col-lg-7">
<table class="tbl table-bordered">
<thead>
<tr>
<th>Col1</th>
<th>Col2</th>
<th>Col3</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
Step III:
And finally ajax call.
Load the table with all data while the page is loading for the first time and then on button click load against the search text.
<script type="text/javascript">
$(function () {
// Loading Entire table on page loading
LoadTable("");
// on search
$('#btnsearch').click(function () {
var searchData = $('#SearchString').val();
if(!searchData){
alert("please enter your text");
return;
}
LoadTable(searchData);
});
function LoadTable(searchString) {
var table = $("table tbody");
var tBody = "";
$.ajax({
type: "POST",
url: "/ControllerName/AssetRegisterIndex",
dataType: "json",
data: { SearchString: searchString },
success: function (data) {
$.each(data, function (idx, elem) {
tBody = tBody + "<tr><td>" + elem.Col1PropertyName + "</td><td>" + elem.Col2PropertyName + "</td><td>" + elem.Col3PropertyName + "</td></tr>";
});
table.html(tBody);
}
})
};
});
</script>
Hope it will help you !!
I need to post back the items that the user added in the DevExpress ListBox, but, according to the company, the way to do it is to store the items in a hidden field and then submit it. I need to know how to create this hidden field, in the view, which, I believe, needs to be a List with Text and Value, (similar to the model passed) and then how to assign the values to it in jquery.
Notes:
1. The question is not how to create a hidden field, but that specific type.
2. The way it is now, in the controller, the model comes back as null.
// This code is located in the Index.cshtml page
<div id="modalMain" class="modal fade hidden-print" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header" style="padding-bottom:0;padding-top:0">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
</div>
<div id="modalMainData" class="modal-body" style=" padding: 0 10px 0 10px !important;">
</div>
</div>
</div>
</div>
// This code is located on ListBoxItemsModal.cshtml
#model List<ValueText>
#using (Html.BeginForm("", "", FormMethod.Post, new { #id = "formPostListBoxItems" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class=" form-group">
#Html.Label("New Item text")
<div class="input-group">
#Html.TextBox("name", null, new { #id = "txtNewListBoxItem" })
<span class="input-group-btn">
<button id="btnAddListBoxItem" type="button" class="btn btn-default btn-xs">Add Item</button>
</span>
</div>
</div>
#Html.DevExpress().ListBox(settings =>
{
settings.Name = "ListBoxCarMake";
settings.Properties.EnableClientSideAPI = true;
settings.Properties.ValueField = "Value";
settings.Properties.ValueType = typeof(string);
settings.Properties.TextField = "Text";
}).BindList(Model).GetHtml()
}
// Add a new item to list box
$(document).on("click", "#btnAddListBoxItem", function () { s = $("#txtNewListBoxItem").val(); ListBoxCarMake.AddItem(s); });
$(document).on("click", "#btnPostListBoxItems", function (e) {
e.preventDefault();
err = '';
$.ajax({
url: '#Url.Action(("PostListBoxItems", "System")',
cache: false,
type: "POST",
data: $("#formPostListBoxItems").serialize(),
success: function (data) { $("#modalMainData").html(data); },
error: function (xhr, status, exception) { DisplayAjaxError(xhr, status, exception); }
});
});
// CONTROLLER
public ActionResult GetListOptions()
{
var model = new List<ValueText>();
model.Add(new ValueText() { Text = "AUDI", Value = "AUDI" });
model.Add(new ValueText() { Text = "BMW", Value = "BMW" });
model.Add(new ValueText() { Text = "VW", Value = "VW" });
return PartialView("~/Views/System/ListBoxItemsModal.cshtml", model);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult PostListBoxItems(List<ValueText> list)
{
return PartialView("~/Views/System/ListBoxItemsModal.cshtml", list);
}
#for (int i = 0; i < Model.Count; i++)
{
#Html.HiddenFor(modelitem => Model[i].Text)
#Html.HiddenFor(modelitem => Model[i].Value)
}
I suggest you to create a ListContainer and append its elements to your html as hidden inputs. This way, when the submit button is pressed, the values will go to the controller.
I have a view partial in a strongly typed controller. Is it possible to render the would-be contents of that view partial on mouseclick?
Example:
Active View
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
</ul>
<div id="modalView"></div>
<script>
$(document).ready(function () {
$('.open-popup-link').magnificPopup({
key: 'my-popup',
type: 'inline',
inline: {
// Define markup. Class names should match key names.
markup: '<div class="white-popup"><div class="mfp-close"></div>awesome</div>'
}
},
{
callbacks: {
open: function(){
}
}
});
$('.open-popup-link').on('mfpOpen', function(e /*, params */) {
var linkText = // how to I grab this? (e.g. 1, 2, 3, or 4)
$.ajax({
// call view partial withlinktext as parameter
//on success
// var inlineContent = viewPartialContent
// On error
// var inlineCOntent = 'Uh oh, something went wrong'
});
});
});
</script>
View Partial
#model *******.Models.Reservation
<div class="container">
<div class="section-heading">
<h2 class="red">Confirm Your Reservation</h2><br />
</div>
<div class="section-content">
<div class="row">
<h3 class="black text-center">Are you sure you want to reserve space <span class="dark-red">#Model.SpaceNumber</span></h3>
<h4 class="black text-center">for <span class="dark-red">#Model.Game.Description</span> on <span class="dark-red">#Model.Game.Date.ToShortDateString()</span>?</h4>
</div>
<div class="row">
<div class="hero-buttons text-center">
No
<form action="/api/Reservations" method="post" id="confirmationForm">
#Html.Hidden("eRaiderUserName", #Model.eRaiderUserName)
#Html.Hidden("SpaceNumber", #Model.SpaceNumber)
<input type="submit" value="Yes" class="btn btn-red btn-lg white">
</form>
</div>
</div>
</div>
</div>
Method for viewpartial in controller
public ActionResult Confirm(int spaceNumber)
{
var reservation = new Reservation { SpaceNumber=spaceNumber, UserName=AppSettings.CurrentUserName, Game=db.Games.FirstOrDefault(g => g.ID == AppSettings.CurrentGameID) };
return View(reservation);
}
Does this make sense, and can I make it work?
You need to do two things:
change your Confirm method, so that it returns PartialView(reservation) instead of View(reservation)
You need to use AJAX, for example jQuery ajax, to get the HTML, and render it in your page
$.ajax({url = 'the url for your Confirm action',
type = 'GET',
dataType='html',
data = params}).done(function(html) {
// use the jQuery and the html to inject it wherever you need in your page
});
NOTE: params is a jQuery object which contains the data you need to pass like spaceNumber in this case, i.e.
var params = { spaceNumber: 'spaceNumberValue' }
I'm using MVC 4 and Entity Framework to develop an intranet web application. I have a list of persons which can be modify by an edit action. I wanted to make my app more dynamic by using modal forms. So I tried to put my edit view into my Bootstrap modal and I have 2 questions about it :
Should I use a simple or a partial view?
How can I perform the validation (actually it work but it redirects me to my original view so not in the modal form)
I think I have to use AJAX and/or jQuery but I'm new to these technologies. Any help would be appreciated.
EDIT : My Index View :
#model IEnumerable<BuSIMaterial.Models.Person>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<br />
<div class="group">
<input type="button" value="New person" class="btn" onclick="location.href='#Url.Action("Create")';return false;"/>
<input type="button" value="Download report" class="btn" onclick="location.href='#Url.Action("PersonReport")';return false;"/>
</div>
#using (Html.BeginForm("SelectedPersonDetails", "Person"))
{
<form class="form-search">
<input type="text" id="tbPerson" name="tbPerson" placeholder="Find an employee..." class="input-medium search-query">
<button type="submit" class="btn">Search</button>
</form>
}
<table class="table">
<thead>
<tr>
<th>Firstname</th>
<th>Lastname</th>
<th>Start Date</th>
<th>End Date</th>
<th>Details</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
#foreach (BuSIMaterial.Models.Person item in ViewBag.PageOfPersons)
{
<tr>
<td>#item.FirstName</td>
<td>#item.LastName</td>
<td>#item.StartDate.ToShortDateString()</td>
<td>
#if (item.EndDate.HasValue)
{
#item.EndDate.Value.ToShortDateString()
}
</td>
<td>
<a class="details_link" data-target-id="#item.Id_Person">Details</a>
</td>
<td>
<div>
<button class="btn btn-primary edit-person" data-id="#item.Id_Person">Edit</button>
</div>
</td>
</tr>
<tr>
<td colspan="6">
<table>
<tr>
<th>National Number</th>
<td>#item.NumNat</td>
</tr>
<tr>
<th>Vehicle Category</th>
<td>#item.ProductPackageCategory.Name</td>
</tr>
<tr>
<th>Upgrade</th><td>#item.Upgrade</td>
</tr>
<tr>
<th>House to work</th>
<td>#item.HouseToWorkKilometers.ToString("G29")</td>
</tr>
</table>
<div id="details_#item.Id_Person"></div>
</td>
</tr>
}
</tbody>
</table>
<div class="modal hide fade in" id="edit-member">
<div id="edit-person-container"></div>
</div>
#section Scripts
{
#Scripts.Render("~/bundles/jqueryui")
#Styles.Render("~/Content/themes/base/css")
<script type="text/javascript" language="javascript">
$(document).ready(function () {
$('#tbPerson').autocomplete({
source: '#Url.Action("AutoComplete")'
});
$(".details_link").click(function () {
var id = $(this).data("target-id");
var url = '/ProductAllocation/ListByOwner/' + id;
$("#details_"+ id).load(url);
});
$('.edit-person').click(function () {
var url = "/Person/EditPerson";
var id = $(this).attr('data-id');
$.get(url + '/' + id, function (data) {
$('#edit-person-container').html(data);
$('.edit-person').modal('show');
});
});
});
</script>
}
My Partial View :
#model BuSIMaterial.Models.Person
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Edit</h3>
</div>
<div>
#using (Ajax.BeginForm("EditPerson", "Person", FormMethod.Post,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "list-of-people"
}))
{
#Html.ValidationSummary()
#Html.AntiForgeryToken()
<div class="modal-body">
<div class="editor-field">
#Html.TextBoxFor(model => model.FirstName, new { maxlength = 50 })
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.LastName, new { maxlength = 50 })
#Html.ValidationMessageFor(model => model.LastName)
</div>
</div>
<div class="modal-footer">
<button class="btn btn-inverse" type="submit">Save</button>
</div>
}
You should use partial views. I use the following approach:
Use a view model so you're not passing your domain models to your views:
public class EditPersonViewModel
{
public int Id { get; set; } // this is only used to retrieve record from Db
public string Name { get; set; }
public string Age { get; set; }
}
In your PersonController:
[HttpGet] // this action result returns the partial containing the modal
public ActionResult EditPerson(int id)
{
var viewModel = new EditPersonViewModel();
viewModel.Id = id;
return PartialView("_EditPersonPartial", viewModel);
}
[HttpPost] // this action takes the viewModel from the modal
public ActionResult EditPerson(EditPersonViewModel viewModel)
{
if (ModelState.IsValid)
{
var toUpdate = personRepo.Find(viewModel.Id);
toUpdate.Name = viewModel.Name;
toUpdate.Age = viewModel.Age;
personRepo.InsertOrUpdate(toUpdate);
personRepo.Save();
return View("Index");
}
}
Next create a partial view called _EditPersonPartial. This contains the modal header, body and footer. It also contains the Ajax form. It's strongly typed and takes in our view model.
#model Namespace.ViewModels.EditPersonViewModel
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Edit group member</h3>
</div>
<div>
#using (Ajax.BeginForm("EditPerson", "Person", FormMethod.Post,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "list-of-people"
}))
{
#Html.ValidationSummary()
#Html.AntiForgeryToken()
<div class="modal-body">
#Html.Bootstrap().ControlGroup().TextBoxFor(x => x.Name)
#Html.Bootstrap().ControlGroup().TextBoxFor(x => x.Age)
</div>
<div class="modal-footer">
<button class="btn btn-inverse" type="submit">Save</button>
</div>
}
Now somewhere in your application, say another partial _peoplePartial.cshtml etc:
<div>
#foreach(var person in Model.People)
{
<button class="btn btn-primary edit-person" data-id="#person.PersonId">Edit</button>
}
</div>
// this is the modal definition
<div class="modal hide fade in" id="edit-person">
<div id="edit-person-container"></div>
</div>
<script type="text/javascript">
$(document).ready(function () {
$('.edit-person').click(function () {
var url = "/Person/EditPerson"; // the url to the controller
var id = $(this).attr('data-id'); // the id that's given to each button in the list
$.get(url + '/' + id, function (data) {
$('#edit-person-container').html(data);
$('#edit-person').modal('show');
});
});
});
</script>
I prefer to avoid using Ajax.BeginForm helper and do an Ajax call with JQuery. In my experience it is easier to maintain code written like this. So below are the details:
Models
public class ManagePeopleModel
{
public List<PersonModel> People { get; set; }
... any other properties
}
public class PersonModel
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
... any other properties
}
Parent View
This view contains the following things:
records of people to iterate through
an empty div that will be populated with a modal when a Person needs to be edited
some JavaScript handling all ajax calls
#model ManagePeopleModel
<h1>Manage People</h1>
#using(var table = Html.Bootstrap().Begin(new Table()))
{
foreach(var person in Model.People)
{
<tr>
<td>#person.Id</td>
<td>#Person.Name</td>
<td>#person.Age</td>
<td>#html.Bootstrap().Button().Text("Edit Person").Data(new { #id = person.Id }).Class("btn-trigger-modal")</td>
</tr>
}
}
#using (var m = Html.Bootstrap().Begin(new Modal().Id("modal-person")))
{
}
#section Scripts
{
<script type="text/javascript">
// Handle "Edit Person" button click.
// This will make an ajax call, get information for person,
// put it all in the modal and display it
$(document).on('click', '.btn-trigger-modal', function(){
var personId = $(this).data('id');
$.ajax({
url: '/[WhateverControllerName]/GetPersonInfo',
type: 'GET',
data: { id: personId },
success: function(data){
var m = $('#modal-person');
m.find('.modal-content').html(data);
m.modal('show');
}
});
});
// Handle submitting of new information for Person.
// This will attempt to save new info
// If save was successful, it will close the Modal and reload page to see updated info
// Otherwise it will only reload contents of the Modal
$(document).on('click', '#btn-person-submit', function() {
var self = $(this);
$.ajax({
url: '/[WhateverControllerName]/UpdatePersonInfo',
type: 'POST',
data: self.closest('form').serialize(),
success: function(data) {
if(data.success == true) {
$('#modal-person').modal('hide');
location.reload(false)
} else {
$('#modal-person').html(data);
}
}
});
});
</script>
}
Partial View
This view contains a modal that will be populated with information about person.
#model PersonModel
#{
// get modal helper
var modal = Html.Bootstrap().Misc().GetBuilderFor(new Modal());
}
#modal.Header("Edit Person")
#using (var f = Html.Bootstrap.Begin(new Form()))
{
using (modal.BeginBody())
{
#Html.HiddenFor(x => x.Id)
#f.ControlGroup().TextBoxFor(x => x.Name)
#f.ControlGroup().TextBoxFor(x => x.Age)
}
using (modal.BeginFooter())
{
// if needed, add here #Html.Bootstrap().ValidationSummary()
#:#Html.Bootstrap().Button().Text("Save").Id("btn-person-submit")
#Html.Bootstrap().Button().Text("Close").Data(new { dismiss = "modal" })
}
}
Controller Actions
public ActionResult GetPersonInfo(int id)
{
var model = db.GetPerson(id); // get your person however you need
return PartialView("[Partial View Name]", model)
}
public ActionResult UpdatePersonInfo(PersonModel model)
{
if(ModelState.IsValid)
{
db.UpdatePerson(model); // update person however you need
return Json(new { success = true });
}
// else
return PartialView("[Partial View Name]", model);
}
In reply to Dimitrys answer but using Ajax.BeginForm the following works at least with MVC >5 (4 not tested).
write a model as shown in the other answers,
In the "parent view" you will probably use a table to show the data.
Model should be an ienumerable. I assume, the model has an id-property. Howeverm below the template, a placeholder for the modal and corresponding javascript
<table>
#foreach (var item in Model)
{
<tr> <td id="editor-success-#item.Id">
#Html.Partial("dataRowView", item)
</td> </tr>
}
</table>
<div class="modal fade" id="editor-container" tabindex="-1"
role="dialog" aria-labelledby="editor-title">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content" id="editor-content-container"></div>
</div>
</div>
<script type="text/javascript">
$(function () {
$('.editor-container').click(function () {
var url = "/area/controller/MyEditAction";
var id = $(this).attr('data-id');
$.get(url + '/' + id, function (data) {
$('#editor-content-container').html(data);
$('#editor-container').modal('show');
});
});
});
function success(data,status,xhr) {
$('#editor-container').modal('hide');
$('#editor-content-container').html("");
}
function failure(xhr,status,error) {
$('#editor-content-container').html(xhr.responseText);
$('#editor-container').modal('show');
}
</script>
note the "editor-success-id" in data table rows.
The dataRowView is a partial containing the presentation of an model's item.
#model ModelView
#{
var item = Model;
}
<div class="row">
// some data
<button type="button" class="btn btn-danger editor-container" data-id="#item.Id">Edit</button>
</div>
Write the partial view that is called by clicking on row's button (via JS $('.editor-container').click(function () ... ).
#model Model
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
<h4 class="modal-title" id="editor-title">Title</h4>
</div>
#using (Ajax.BeginForm("MyEditAction", "Controller", FormMethod.Post,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "editor-success-" + #Model.Id,
OnSuccess = "success",
OnFailure = "failure",
}))
{
#Html.ValidationSummary()
#Html.AntiForgeryToken()
#Html.HiddenFor(model => model.Id)
<div class="modal-body">
<div class="form-horizontal">
// Models input fields
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
}
This is where magic happens: in AjaxOptions, UpdateTargetId will replace the data row after editing, onfailure and onsuccess will control the modal.
This is, the modal will only close when editing was successful and there have been no errors, otherwise the modal will be displayed after the ajax-posting to display error messages, e.g. the validation summary.
But how to get ajaxform to know if there is an error? This is the controller part, just change response.statuscode as below in step 5:
the corresponding controller action method for the partial edit modal
[HttpGet]
public async Task<ActionResult> EditPartData(Guid? id)
{
// Find the data row and return the edit form
Model input = await db.Models.FindAsync(id);
return PartialView("EditModel", input);
}
[HttpPost, ValidateAntiForgeryToken]
public async Task<ActionResult> MyEditAction([Bind(Include =
"Id,Fields,...")] ModelView input)
{
if (TryValidateModel(input))
{
// save changes, return new data row
// status code is something in 200-range
db.Entry(input).State = EntityState.Modified;
await db.SaveChangesAsync();
return PartialView("dataRowView", (ModelView)input);
}
// set the "error status code" that will redisplay the modal
Response.StatusCode = 400;
// and return the edit form, that will be displayed as a
// modal again - including the modelstate errors!
return PartialView("EditModel", (Model)input);
}
This way, if an error occurs while editing Model data in a modal window, the error will be displayed in the modal with validationsummary methods of MVC; but if changes were committed successfully, the modified data table will be displayed and the modal window disappears.
Note: you get ajaxoptions working, you need to tell your bundles configuration to bind jquery.unobtrusive-ajax.js (may be installed by NuGet):
bundles.Add(new ScriptBundle("~/bundles/jqueryajax").Include(
"~/Scripts/jquery.unobtrusive-ajax.js"));
In $('.editor-container').click(function (){}), shouldn't var url = "/area/controller/MyEditAction"; be var url = "/area/controller/EditPartData";?