J'utilise MVC 4 et Entity Framework pour développer une application Web intranet. J'ai une liste de personnes qui peuvent être modifiées par une action d'édition. Je voulais rendre mon application plus dynamique en utilisant des formulaires modaux. J'ai donc essayé de mettre ma vue d'édition dans mon Bootstrap modal) et j'ai 2 questions à ce sujet:
Je pense que je dois utiliser AJAX et/ou jQuery mais je suis novice dans ces technologies. Toute aide serait la bienvenue.
EDIT: Ma vue d'index:
@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>
}
Ma vue partielle:
@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>
}
Vous devriez utiliser des vues partielles. J'utilise l'approche suivante:
Utilisez un modèle de vue pour que vos modèles de domaine ne soient pas transmis à vos vues:
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; }
}
Dans votre 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");
}
}
Créez ensuite une vue partielle appelée _EditPersonPartial
. Ceci contient l'en-tête modal, le corps et le pied de page. Il contient également le formulaire Ajax. Il est fortement typé et prend en compte notre modèle de vue.
@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>
}
Maintenant, quelque part dans votre application, dites un autre _peoplePartial.cshtml partiel, 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>
Je préfère éviter d'utiliser Ajax.BeginForm
aide et fais un appel Ajax avec JQuery. Dans mon expérience, il est plus facile de maintenir un code écrit comme ceci. Donc, ci-dessous sont les détails:
Modèles
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
}
vue parent
Cette vue contient les éléments suivants:
@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>
}
vue partielle
Cette vue contient un modal qui sera rempli avec des informations sur une personne.
@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" })
}
}
Actions du contrôleur
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);
}
En réponse à la réponse de Dimitrys mais en utilisant Ajax.BeginForm
, Ceci fonctionne au moins avec MVC> 5 (4 non testés).
écrire un modèle comme indiqué dans les autres réponses,
Dans la "vue parent", vous utiliserez probablement un tableau pour afficher les données. Le modèle devrait être un ienumerable. Je suppose que le modèle a une propriété id
-. Cependant, sous le modèle, un espace réservé pour le modal et le javascript correspondant.
<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>
notez "editor-success-id" dans les lignes de la table de données.
Le dataRowView
est un partiel contenant la présentation d'un élément de modèle.
@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>
Écrivez la vue partielle appelée en cliquant sur le bouton de la ligne (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>
}
C'est là que la magie opère: dans AjaxOptions
, UpdateTargetId remplacera la ligne de données après édition, onfailure et onsuccess contrôleront le modal.
Ceci signifie que le modal ne se fermera que si l'édition a réussi et qu'il n'y a pas eu d'erreur, sinon le modal sera affiché après l'affichage ajax pour afficher les messages d'erreur, par exemple le récapitulatif de validation.
Mais comment obtenir ajaxform pour savoir s’il ya une erreur? C’est la partie contrôleur, il suffit de modifier response.statuscode comme indiqué à l’étape 5 ci-dessous:
la méthode d'action de contrôleur correspondante pour la modale d'édition partielle
[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);
}
Ainsi, si une erreur survient lors de l'édition des données de modèle dans une fenêtre modale, l'erreur sera affichée dans le modal avec les méthodes validationsummary de MVC; toutefois, si les modifications ont été validées, la table de données modifiée sera affichée et la fenêtre modale disparaît.
Remarque: pour que ajaxoptions fonctionne, vous devez indiquer à la configuration de votre bundle de lier jquery.unobtrusive-ajax.js
(Peut être installé par NuGet):
bundles.Add(new ScriptBundle("~/bundles/jqueryajax").Include(
"~/Scripts/jquery.unobtrusive-ajax.js"));