Traffy Traffy - 7 months ago 454
Javascript Question

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>
}

Answer

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>