MVC 4 Редактирование модальной формы с использованием Bootstrap
Я использую MVC 4 и Entity Framework для разработки веб-приложения в интрасети. У меня есть список лиц, которые могут быть изменены действием редактирования. Я хотел сделать приложение более динамичным, используя модальные формы. Поэтому я попытался поместить свое редактирование в мой модальный модуль Bootstrap, и у меня есть 2 вопроса:
- Должен ли я использовать простой или частичный вид?
- Как выполнить проверку (на самом деле она работает, но она перенаправляет меня на исходное представление, а не в модальную форму).
Я думаю, что мне нужно использовать AJAX и/или jQuery, но я новичок в этих технологиях. Любая помощь будет оценена.
EDIT: Мой индекс:
@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="[email protected]_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>
}
Мой частичный вид:
@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>
}
Ответы
Ответ 1
Вы должны использовать частичные виды. Я использую следующий подход:
Используйте модель представления, чтобы вы не передавали свои модели домена в свои представления:
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; }
}
В 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");
}
}
Затем создайте частичное представление под названием _EditPersonPartial
. Это содержит модальный заголовок, тело и нижний колонтитул. Он также содержит форму Ajax. Он строго типизировал и отображает нашу модель.
@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>
}
Теперь где-то в вашем приложении скажите еще один частичный _peoplePartial.cshtml и т.д.:
<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 given to each button in the list
$.get(url + '/' + id, function (data) {
$('#edit-person-container').html(data);
$('#edit-person').modal('show');
});
});
});
</script>
Ответ 2
Я предпочитаю избегать использования помощника Ajax.BeginForm
и выполнять вызов Ajax с помощью JQuery. По моему опыту легче поддерживать код, написанный так. Ниже приведены детали:
Модели
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
}
Родительский просмотр
Этот вид содержит следующие вещи:
- записи людей для итерации через
- пустой div, который будет заполнен модальным, когда Person должен быть отредактирован
- некоторый JavaScript, обрабатывающий все вызовы ajax
@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>
}
Частичный вид
Этот вид содержит модальный, который будет заполнен информацией о человеке.
@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" })
}
}
Действия с контроллером
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);
}
Ответ 3
В ответ на ответ Димитриуса, но используя Ajax.BeginForm
, работает как минимум с MVC > 5 (4 не тестировалось).
-
напишите модель, как показано в других ответах,
-
В "родительском представлении" вы, вероятно, будете использовать таблицу для отображения данных.
Модель должна быть ienumerable. Я предполагаю, что модель имеет id
-property. Тем не менее ниже шаблона, placeholder для модального и соответствующего javascript
<table>
@foreach (var item in Model)
{
<tr> <td id="[email protected]">
@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>
обратите внимание на "идентификатор-успех-идентификатор" в строках таблицы данных.
-
dataRowView
является частичным, содержащим представление элемента модели.
@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>
-
Напишите частичный вид, который вызывается нажатием кнопки строки (через 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>
}
Здесь происходит волшебство: в AjaxOptions
UpdateTargetId заменит строку данных после редактирования, onfailure и onsuccess будет управлять модальным.
Это означает, что модальность будет закрыта только при успешном редактировании, и ошибок не было, иначе модальный текст будет отображаться после сообщения ajax-post, чтобы отображать сообщения об ошибках, например. сводка валидации.
Но как получить ajaxform, чтобы узнать, есть ли ошибка? Это часть контроллера, просто измените код response.status, как показано ниже в шаге 5:
-
соответствующий метод действия контроллера для частичного редактирования модального
[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);
}
Таким образом, при возникновении ошибки при редактировании данных модели в модальном окне ошибка будет отображаться в модальном режиме с помощью методов validationsummary MVC; но если изменения были успешно выполнены, отобразится измененная таблица данных и исчезнет модальное окно.
Примечание: вы получаете работу ajaxoptions, вам нужно сообщить, что ваша конфигурация связок связывает jquery.unobtrusive-ajax.js
(может быть установлена NuGet):
bundles.Add(new ScriptBundle("~/bundles/jqueryajax").Include(
"~/Scripts/jquery.unobtrusive-ajax.js"));