mvc ajax submitting from a modal - c#

I call a modal which allows a user to select a bunch of dynamically created check boxes and then submit the form to a controller which saves all the info from the FormCollection passed in to it then does a RedirectToAction to the page that calls the modal.
I want to be able to still to save the stuff in the form, but, instead of redirecting to the page that calls the modal i want to stay in the modal.
modal
<script type="text/javascript">
// Close Modal when done.
function CloseModal() {
$("#SkillModalWindow").modal("hide");
}
</script>
#using (Ajax.BeginForm("Save", "SkillGroup", null, new AjaxOptions
{
HttpMethod = "Post",
OnSuccess = "CloseModal"
},
new { id = "CreateSkillGroups" }))
{
#Html.ValidationSummary(true)
#Html.Hidden("JobRoleId", (int)ViewBag.JobRoleID)
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="myModalLabel">Add New Skills to Job Role</h3>
</div>
<div class="modal-body" id="CreateModal">#Html.Partial("_Create")</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<button type="submit" class="btn btn-primary">Save</button>
</div>
}
Controllor Action
[HttpPost]
public ActionResult Save(FormCollection formCollection)
{
foreach (var key in formCollection.AllKeys)
{
do stuff.....
}
return RedirectToAction("Index");
}

You could return a partial view containing the modal markup instead of redirecting:
[HttpPost]
public ActionResult Save(FormCollection formCollection)
{
foreach (var key in formCollection.AllKeys)
{
do stuff.....
}
return PartialView("_Modal");
}
Also make sure that you have included the jquery.unobtrusive-ajax.js script in your main view if you want the Ajax.BeginForm helper to work.

Related

How to manage multiple submit button in my case in MVC 5?

I am a beginner to MVC, I am using MVC 5
_Layout.cshtml code
_Layout.cshtml has navbar, it contains Logout, More button if user already loggedin, Also, it will render body part(Index.cshtml)
//Navbar
#if (ViewBag.name != null)
{
<input type="submit" class="dropdown-item" formaction="Logout" value="Logout" id="btnLogout" />
}
else{
<a class="nav-link" href="#">Login & Signup</a>
}
<input type="submit" class="dropdown-item" formaction="More" value="More" id="btnMore" />
//body
<div class="container body-content">
#RenderBody() //Index.cshtml
</div>
Index.cshtml
Index.cshtml it has some links and also some buttons and anchor links, and it will call partial view (_Login)
#using TestProject.Models
#model ViewSignInAndSignUp
//some html code
<input type="submit" class="dropdown-item" formaction="Action1" value="Action1" id="btnAction1" />
<input type="submit" class="dropdown-item" formaction="Action2" value="Action1" id="btnAction2" />
#Html.Partial("_Login") //partialview
_Login.cshtml
_Login.cshtml this one partial view, if user visit any links, this partial view will popup to request the user to make login. This partial view buttons are working
#model TestProject.Models.SignIn
#using (Html.BeginForm("ControllerSignIn", "Home"))
{
#Html.TextBoxFor(si => si.userName, new { #class = "form-control", #id = "txtLogin" })
#Html.TextBoxFor(si => si.password, new { #class = "form-control", #id = "txtPassword", #type = "password" })
<input type="submit" class="btn btn-sm btn-primary btn-rounded" value="Login" id="btnLoginSubmit" />
}
HomeController
[HttpPost]
public ActionResult Logout(string submitButton)
{
// some coding
return RedirectToAction("Index", "Home");
}
[HttpPost]
public ActionResult More(string str1)
{
// some coding
return RedirectToAction("Index", "Home");
}
[HttpPost]
public ActionResult Action1(string str1)
{
// some coding
return RedirectToAction("Index", "Home");
}
[HttpPost]
public ActionResult Action2(string str1)
{
// some coding
return RedirectToAction("Index", "Home");
}
Now, How can I manage all the buttons from _Layout.cshtml, Index.cshtml, _Login.cshtml(Partial view)
Note
my login button is working, but, logout, more, action1, action2 buttons are not working
Replace this
<input type="submit" class="dropdown-item" formaction="Logout" value="Logout" id="btnLogout" />
with this
Logout
Replace the href with the logout endpoint for your application if different than stated above. Remove any form pointing you to the logout endpoint also as it is not needed anymore.
The submit button submits the form using action URL defined in <form> tag, if you want to use multiple submit buttons then you need to wrap them inside multiple forms with different values of action attribute.
However, there's no need to use multiple submit buttons for those action methods, because redundant <form> tags are inefficient. You may just use #Html.ActionLink() helper to call them like this (and then use styling for those links to look like a button):
#Html.ActionLink("Logout", "Logout", "Home", null, new { #class = "dropdown-item", #id = "btnLogout" })
If the action contains parameter(s), you should add routeValues like example below:
#Html.ActionLink("Action1", "Action1", "Home", new { str1 = "SomeValue" }, new { #class = "dropdown-item", #id = "btnAction1" })
And change HTTP method to GET because ActionLink helper generates anchor <a> tag with href attribute which should use GET method:
[HttpGet]
public ActionResult Logout()
{
// some coding
return RedirectToAction("Index", "Home");
}
The same treatment should be applied to More, Action1 and Action2 action methods as well.
Further reference:
ActionLink helper

Update main view after ajax rendered a partial view

I have a list of "apps" that users can run. Each app targets a specific API of ours to demonstrate what the API does. Some of these apps require user input so that I can pass user-given parameters into the API.
Each app is responsible for generating the HTML that represents its output. For Apps that do not require any input, it's a straight forward process of executing them in a controller/action from an ajax request, and updating the view with the output.
The challenge is wiring up user input support. I've managed to get 90% of the way there and have hit a roadblock. The apps are responsible for instantiating their own view model. Using a bit of convention, each App has an associated partial view that is located under the same path that the app's namespace is. This lets me create a view model for the app, and return the partial view for each app like this:
public ActionResult GetViewModel(string appId)
{
IApp app = AppFactory.GetAppById(appId);
string path = app.GetType().FullName.Replace('.', '/');
return PartialView($"~/Views/{path}.cshtml", app.CreateViewModel());
}
An example partial view, using an app supplied view model, looks like this:
#using Examples.DataAccess.Query;
#model Query_02_ParameterizedQueryViewModel
#using (#Html.BeginForm("RunAppFromViewModel", "Home", FormMethod.Post))
{
#Html.ValidationSummary(true)
<fieldset>
<div class="form-inline">
<div class="form-group">
#Html.LabelFor(viewModel => viewModel.City)
#Html.EditorFor(viewModel => viewModel.City, new { placeholder = "Phoenix" })
#Html.ValidationMessageFor(viewModel => viewModel.City)
#Html.HiddenFor(viewModel => viewModel.AppId)
</div>
</div>
<button class="btn btn-default" type="submit">Run</button>
</fieldset>
}
The main view has a button that opens a modal bootstrap dialog. When the dialog is opened, I make an ajax request to the server to get the view model and partial view. I then insert the partial view into the modal dialog and update the client-side validation so it works with the unobtrustive stuff. The problem however is that when the form is posted back to the server, and the outputted HTML from the app is returned from the server to the client, I dont know how to update the main view with it.
For example, this is the Main view and the JavaScript that handles both the View Model based Apps and the non-VM based apps.
#using Examples.Browser.ViewModels;
#using Examples.Browser.Models;
#{
ViewBag.Title = "Home Page";
}
#model List<ApiAppsViewModel>
<div class="jumbotron">
<h1>Framework API Micro-Apps</h1>
<p class="lead">Micro-apps provide a way to execute the available APIs in the Framework, without having to write any code, and see the results.</p>
</div>
<div class="container">
<h3 class="text-center">Available API Apps</h3>
<div class="table-responsive">
<table class="table table-hover table-responsive">
<tr>
<th>#nameof(ApiApp.Components)</th>
<th>#nameof(ApiApp.Name)</th>
<th>#nameof(ApiApp.Description)</th>
<th>Options</th>
</tr>
#foreach (ApiAppsViewModel app in this.Model)
{
<tr>
<td>#string.Join(",", app.Components)</td>
<td>#app.Name</td>
<td>#app.Description</td>
<td>
#if (app.IsViewModelRequired)
{
<button type="button"
data-app="#app.Id.ToString()"
data-vm-required="#app.IsViewModelRequired"
data-app-name="#app.Name"
data-toggle="modal"
data-target="#appParameters"
class="btn btn-success">
Run App
</button>
}
else
{
<button type="button"
data-app="#app.Id.ToString()"
data-vm-required="#app.IsViewModelRequired"
class="btn btn-success">
Run App
</button>
}
</td>
</tr>
<tr class="hidden">
<td colspan="4">
<div class="container alert alert-info" data-app="#app.Id.ToString()">
</div>
</td>
</tr>
}
</table>
</div>
</div>
<div class="modal fade"
id="appParameters"
role="dialog"
aria-labelledby="appParametersLabel">
<div class="modal-dialog"
role="document">
<div class="modal-content">
<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="appParametersLabel"></h4>
</div>
<div class="modal-body" id="appDialogBody">
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript">
$('.btn-success').click(function () {
var button = $(this);
var appId = $(this).data("app");
var vmRequired = $(this).data("vm-required");
if (vmRequired == "False") {
var url = "/Home/RunApp?appId=" + appId;
$.get(url, function (data) {
$("div[data-app='" + appId + "']").html(data);
var buttonColumn = button.parent();
var appRow = buttonColumn.parent();
var hiddenRow = appRow.next()
hiddenRow.removeClass("hidden");
appRow.click(function () {
var hiddenColumn = hiddenRow.children().first();
var resultsDiv = hiddenColumn.children().first();
resultsDiv.empty();
hiddenRow.addClass("hidden");
$(this).off();
hiddenRow.off();
})
hiddenRow.click(function () {
var hiddenColumn = $(this).children().first();
var resultsDiv = hiddenColumn.children().first();
resultsDiv.empty();
$(this).addClass("hidden");
appRow.off();
$(this).off();
})
});
} else {
var appName = $(this).data("app-name");
$('#appParametersLabel').html(appName);
var url = "/Home/GetViewModel?appId=" + appId;
$.get(url, function (data) {
$('#appDialogBody').html(data);
var dialog = $('#appDialogBody');
$.validator.unobtrusive.parse(dialog);
});
$('#appParameters').modal({
keyboard: true,
backdrop: "static",
show: false,
}).on('show', function () {
});
}
});
</script>
When there isn't a view model needed, I stuff the results in an invisible row and make the row visible. Since the View Model apps have their form data submitted from a partial view, when I return the HTML from the controller, it renders it out as raw text to the browser. I assume I can write some java script to handle this but i'm not sure what that would look like. How do I get the form post from the partial view, to return the HTML it generates back to the invisible row within the main view?
This is the controller action that the form posts to, and returns, along with the controller action non-view model based apps use to run their apps and generate the HTML.
[HttpGet]
public async Task<string> RunApp(string appId)
{
IApp app = AppFactory.GetAppById(appId);
if (app == null)
{
return "failed to locate the app.";
}
IAppOutput output = await app.Run();
if (output == null)
{
return "Failed to locate the app.";
}
return output.GetOutput();
}
[HttpPost]
public async Task<string> RunAppFromViewModel(FormCollection viewModelData)
{
IApp app = AppFactory.GetAppById(viewModelData["AppId"]);
foreach(PropertyInfo property in TypePool.GetPropertiesForType(app.ViewModel.GetType()))
{
property.SetValue(app.ViewModel, viewModelData[property.Name]);
}
IAppOutput output = await app.Run();
return output.GetOutput();
}
If you want to update the existing page with the data returned by the RunAppFromViewModel() method, then you need to submit your form using ajax. Since the form is loaded dynamically after the initial page has been loaded, you need to use event delegation. You will also need to store the element you want updated when the form is loaded.
var hiddenRow;
$('.btn-success').click(function () {
// store the element to be updated
hiddenRow = $(this).closest('tr').next('tr').find('.container');
....
});
// Handle the submit event of the form
$('#appDialogBody').on('submit', 'form', function() {
// check if the form is valid
if (!$(this).valid())
{
return;
}
var formData = $(this).serialize(); // serialize the forms controls
var url = '#Url.Action("RunAppFromViewModel", "Home");
$.post(url, formData , function(response) {
hiddenRow.html(response); // assumes your returning html
});
return false; // cancel the default submit
});

Send string from View to Controller using httpGet - MVC

I am new to MVC so please bear with me.
I am trying to send a string from a textbox to a controller method so I can find an object in a database. However, I do not know how to send the string successfully from the view to the controller in a HttpGet request (only in HttpPost)
The code in my view
<div>
<label>Email</label>
#Html.TextBox("email")
</div>
<div class="btn btn-success">
#Html.ActionLink("Edit RSVP", "Edit")
</div>
The ViewResult method in my controller
// Problem is the email parameter is always null
[HttpGet]
public ViewResult Edit(string email)
{
// If the email the typed is find, it will display their contents on to a RsvpForm view
return View("RsvpForm", guestRepository.Find(email));
}
Anyone know how I can send this string through, I would be grateful.
Thanks
Like this:
#using (Html.BeginForm("Edit", "ControllerName", FormMethod.Get))
{
<div>
<label>Email</label>
#Html.TextBox("email")
</div>
<div class="btn btn-success">
<input type="submit" value="Edit RSVP" />
</div>
}
Note: I can't tell from your description whether or not you are trying to do this without reloading the page. This option will post the page to the controller, so you will get a page reload.
If you want this to load without posting the page, you can look into Ajax.BeginForm. Here is a StackOverflow article with a decent primer on the AJAX form.
update
For your example, you may could do something like this if you want to use AJAX. This is all untested, but may be close to what you would need.
First you can create a partial view that represents the user data that you want to display:
RsvpForm.cshtml
#model GuestData
<div class="hdr">Name</div>
<div class="value">#Model.Name</div>
<div class="hdr">Email</div>
<div class="value">#Model.Email</div>
Then you want to make sure that your controller returns the partial view based on the email that is sent via the GET:
GuestDataController.cs
[HttpGet]
public ActionResult Edit(string email)
{
// If the email the typed is find, it will display their contents on to a RsvpForm view
return PartialView("RsvpForm", guestRepository.Find(email));
}
Then you create the AJAX form to submit the request via a GET and load the partial view without reloading the page: view.cshtml
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.js")" type="text/javascript"></script>
#using (Ajax.BeginForm("Edit", "GuestData", null, new AjaxOptions { UpdateTargetId = "UserData", HttpMethod = "Get" }, null))
{
<div>
<label>Email</label>
#Html.TextBox("email")
</div>
<div class="btn btn-success">
<input type="submit" value="Edit RSVP" />
</div>
}
<div id="UserData"></div>
The easiest way to do it is to create a form as follow :
#using(Html.BeginForm("Edit", ControllerName, FormMethod.GET))
{
#Html.Label("Email")
#Html.TextBox("email")
<input type="submit" value="Edit RSVP"/>
}
or you can use Jquery to change the link when textbox value change (which I do not recommend):
$('input[name=email]').on('change' function()
{
var value = $(this).val();
var href = $('.btn').next('a').attr('href');
href += '?email='+value;
$('.btn').next('a').attr('href', href)
});

Rendering partial view inside a view using AJAX

I'm trying to refresh a partial view inside of a view when a form is submitted. However, whenever I try it just renders the partial view as a normal view. Can someone tell me what I'm doing wrong?
Controller:
public ActionResult ChangeHeatName(string heatName, string updatedHeat)
{
string user = User.Identity.Name;
HomeModel H = new HomeModel();
H.ChangeHeatName(heatName, updatedHeat, user);
ChemViewModel mySlagViewModel = new ChemViewModel();
mySlagViewModel = H.QueryResults(heatName);
return PartialView("PartialChemAnalysis", mySlagViewModel);
}
Partial view form (contained in partial view, not main view):
#using (Ajax.BeginForm("ChangeHeatName", "Home", new AjaxOptions(){UpdateTargetId = "chemDiv" InsertionMode = InsertionMode.Replace}))
{
<section>
Heat Name:<input type="text" name="heatName" value="#Html.ValueFor(x => x.heatname)" style ="width:100px"/>
Change to:<input type="text" name="updatedHeat" value="" style="width: 100px" />
<input type="submit" name="ChangeHeatName" value="Change" />
</section>
}
Index view where partial view is being rendered:
#if(ViewBag.SearchKey == null)
{
<div class="content-wrapper">
<hgroup class="title">
<h1>#HttpContext.Current.User.Identity.Name</h1>
<h2>#ViewBag.Message</h2>
</hgroup>
</div>
}
#using (Html.BeginForm("Index", "Home", "POST"))
{
<div class="searchField">
<input type="text" class="search-query" name="heatSearch" placeholder="Search">
<button class="btn btn-success" type="submit">Search</button>
<br />
#if (ViewBag.AverageSuccessful == true)
{
<input type="text" name="AvgConfirmation" class="search-query" value="Average Submitted Successfully" width:"400px" placeholder="Search" />
}
</div>
}
#if(ViewBag.SearchKey != null)
{
<div>
<div id ="chemDiv">
#Html.Action("PartialChemAnalysis", "Home", (string)ViewBag.SearchKey)
</div>
<div id ="slafDiv">
#Html.Action("PartialSlagView", "Home", (string)ViewBag.SearchKey)
</div>
</div>
}
Index controller that passes SearchKey:
[HttpPost]
public ActionResult Index(string heatSearch)
{
ViewBag.SearchKey = heatSearch;
return View();
}
Currently your ajax.beginform is in your partial view, that's all fine and dandy, but your partialview is not rendered inside your index, so really your never doing the ajax replace logic you're just calling a the action method and getting a full page refresh of the partial view.
here's what would work.
#if(ViewBag.SearchKey != null)
{
<div>
<div id ="chemDiv">
#Html.Partial("ChangeHeatName")
</div>
<div id ="slafDiv">
#Html.Action("PartialSlagView", "Home", (string)ViewBag.SearchKey)
</div>
</div>
}
Now your Ajax.Beginform is rendered in the index view, and when the button is clicked it will refresh.
Edit: you'll need to do something with #Html.Action("PartialChemAnalysis", "Home", (string)ViewBag.SearchKey) possibly stick it in your partial view, because everything in "chemDiv" will now be replaced upon update.
You're not specifying POST in Ajax.BeginForm(). Try this:
#using (Ajax.BeginForm("ChangeHeatName", "Home", FormMethod.Post,
new AjaxOptions(){UpdateTargetId = "chemDiv" InsertionMode = InsertionMode.Replace}))
{...}
Also, stick a breakpoint on your controller action and step through it, see if it's actually hitting return PartialView() or skipping over it.
Posting this because it's not an intuitive fix. Apparently there are issues with MVC 4 and jQuery 1.9.1 so to get this to work I had to change my reference to jQuery 1.7.1

MVC 4 Edit modal form using Bootstrap

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";?

Categories