Combine two Action Result MVC Scaffold - c#

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

Related

Issue with my voting system in C# asp.net MVC project. Idea is based on StackOverflow voting system

I am trying to implement a voting system on answers post just like StackOverflow. I am able to do ajax request and everything is stored perfectly as the desired way in the database. But I am facing an issue with my View. In my view, all the answers's Html is rendered with one for loop. In this Html element, I am selecting answer by div class called vote with jQuery and doing my ajax call and updating my vote count. But the issue is when I am upvoting one answer, Instead of increasing only its vote count, it increases all the answers vote count at the same time.
I think the mistake in selecting all the div with the vote class name. But I am not able to find a way to select them separately to increase my VoteCount on the selected answers only.
Here is my View Code.
#model AskMe.Models.ViewModel.QuestionDetailViewModel
#{
ViewBag.Title = "QuestionDetails";
}
<h1>Question Details: </h1>
<h3>#Model.question.Post.Title</h3>
#Html.Raw(Model.question.Post.Content)
<hr />
<h3>Answers:</h3>
<div id="paginationContainer">
#foreach (var answer in Model.question.Answers)
{
<div class="card bg-light m-1">
<div class="card-body">
<h5 class="card-title">Answered by: #answer.Post.CreatedBy.UserName</h5>
#Html.Raw(answer.Post.Content)
</div>
<div class="col vote">
<div>
<button data-vote-id="#answer.Post.PostId" class="nav-link thumbs-up" href=""><i class="fa fa-thumbs-up" aria-hidden="true"></i></button>
</div>
<div class="Count">
#(answer.Post.UpVotes.Count-answer.Post.DownVotes.Count)
</div>
<div>
<button data-vote-id="#answer.Post.PostId" class="nav-link thumbs-down" href=""><i class="fa fa-thumbs-down" aria-hidden="true"></i></button>
</div>
</div>
#Html.ActionLink("View Comments", "Index", "Comments", new { answerId = answer.PostId }, null) |
</div>
}
</div>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h3>Contribute your answer: </h3>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(m => m.AnswerContent, new { #class = "col-md-2 control-label" })
<div class="col-md-10">
#Html.TextAreaFor(m => m.AnswerContent, new { #class = "form-control" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
#Html.HiddenFor(m => m.UserId)
#Html.HiddenFor(m => m.QuestionId)
#Html.HiddenFor(m => m.question)
<input type="submit" value="Contribute now" class="btn btn-outline-primary" />
</div>
</div>
</div>
}
#section Scripts {
#Scripts.Render("~/bundles/jqueryval");
<script type="text/javascript">
$(document).ready(function () {
tinyMCE.init({
mode: "textareas",
});
$('#paginationContainer').buzinaPagination({
itemsOnPage: 3,
prevnext: true,
prevText: "Previous",
nextText: "Next"
});
$(".vote").on("click", ".thumbs-up", function () {
$.ajax({
url: "/api/votes/upvote/" + $(this).attr("data-vote-id"),
method: "POST",
success: function () {
var countTxt = $(".Count").text();
console.log(countTxt);
var count = parseInt(countTxt);
$(".Count").text(count + 1);
console.log(count);
}
})
})
$(".vote").on("click", ".thumbs-down", function () {
$.ajax({
url: "/api/votes/downvote/" + $(this).attr("data-vote-id"),
method: "POST",
success: function () {
var countTxt = $(".Count").text();
console.log(countTxt);
var count = parseInt(countTxt);
$(".Count").text(count - 1);
console.log(count);
}
})
})
});
</script>
}
and here is my ViewModel
public class QuestionDetailViewModel
{
ApplicationDbContext _context = new ApplicationDbContext();
public virtual Question question { get; set; }
[Display(Name = "Add your answer.")]
public string AnswerContent { get; set; }
public string UserId { get; set; }
public int QuestionId { get; set; }
public int AnswerId { get; set; }
public int VoteCount { get; set; }
}
You are selecting and setting all .Count elements as far as I can see in
var countTxt = $(".Count").text();
...
$(".Count").text(count - 1);
While we should handle only those .Count elements that are related to the current answer.
It can be done with ancestor + find selector
var $count = $(this).ancestor(".vote").children(".Count")[0];
$.ajax({
url: "/api/votes/downvote/" + $(this).attr("data-vote-id"),
method: "POST",
success: function () {
var countTxt = $count.text();
...
$count.text(count - 1);
Thanks, #EugenePodskal to point out my mistake and show me the correct direction for the solution. Here is the solution for question.
$(".vote").on("click", ".thumbs-up", function () {
var counter = $(this).closest(".vote").children(".Count");
$.ajax({
url: "/api/votes/upvote/" + $(this).attr("data-vote-id"),
method: "POST",
success: function () {
var countTxt = counter.text();
console.log(countTxt);
var count = parseInt(countTxt);
counter.text(count + 1);
console.log(count);
}
})
})
Thanks again #EUgenePodskal for your help and time.

MVC Bootstrap form-control DropDownListFor validation error message not displaying

I am using MVC5 and I can not get the MVC validation message for DropDownList (with Bootsrap3). I researched similar problems on stackoverflow but any solutions work for me.
I want to user select option from drop down list. If he click "Submit" button and no option was selected he should get validation error message. Initially when page is loaded first time, any option is selected (selected is option with value "null").
Model:
public class CarsPriceSearchViewModel
{
[Required(ErrorMessage = "Proszę wybrać markę"), Range(1, Int32.MaxValue)]
[Display(Name = "Marka")]
public int SelectedBrandId { get; set; }
public SelectList Brands { get; set;}
[Required(ErrorMessage = "Proszę wybrać model"), Range(1, Int32.MaxValue)]
[Display(Name = "Model")]
public int SelectedModelId { get; set; }
}
Controller
public ActionResult Index()
{
oSelectList = new List<SelectListItem>() {
new SelectListItem(){ Value="1", Text="Audi"},
new SelectListItem(){ Value="2", Text="BMW"}
};
oSearchViewModel.Brands = new SelectList(oSelectList, "Value", "Text");
return View(oSearchViewModel);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Search(CarsPriceSearchViewModel searchOptions)
{
if (ModelState.IsValid)
{
//TO sth
}
return View(searchOptions);
}
View
<div class="panel-body">
#using (Html.BeginForm("Search", "Home", FormMethod.Post, new { id = "SearchCarsOffersForm" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<div class="row">
<div class="col-lg-2">
<div class="form-group">
#Html.LabelFor(m => m.SelectedBrandId)
#Html.DropDownListFor(m => m.SelectedBrandId,
Model.Brands,
"--Wybierz-Markę--",
new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.SelectedBrandId, "", new { #class = "text-danger" })
</div>
</div>
<div class="col-lg-2">
<div class="form-group">
#Html.LabelFor(m => m.SelectedModelId)
#Html.DropDownListFor(m => m.SelectedModelId,
new SelectList(Enumerable.Empty<SelectListItem>(), "ModelId", "ModelName"),
"--Wybierz-Model--",
new { #class = "form-control" })
#Html.ValidationMessageFor(m => m.SelectedModelId, "", new { #class = "text-danger" })
</div>
</div>
</div>
<div class="panel-footer">
<button type="submit" id="SearchCarsOffers" class="btn btn-outline btn-primary btn-lg btn-block">
Szukaj
</button>
</div>
}
</div>
Remark
In _Layout.cshtml I have included scripts:
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>
<script src="/Scripts/jquery-1.10.2.js"></script>
<script src="/Scripts/jquery.validate.js"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js"></script>
Scripts
$("#SelectedBrandId").change(function (event) {
$.ajax({
url: "UsedCarsPriceChart/FillBrandModels/",
data: { id: $(this).val() /* add other additional parameters */ },
cache: false,
type: "POST",
dataType: "JSON",
success: function (models) {
$("#SelectedModelId").html(""); // clear before appending new list
$("#SelectedModelId").append(
$('<option></option>').html("--Wybierz-Model--"));
for (var i = 0; i < models.length; i++) {
var model = models[i];
$("#SelectedModelId").append(
$('<option></option>').val(model.ModelId).html(model.ModelName));
}
//The same statement as above
//$.each(models, function (i, model) {
// $("#SelectedModelId").append(
// $('<option></option>').val(model.ModelId).html(model.ModelName));
//});
$("#VersionModelId").html(""); // clear before appending new list
$("#VersionModelId").append(
$('<option></option>').html("--Wybierz-Wersję--"));
}
});
});
$("#SelectedModelId").change(function (event) {
$.ajax({
url: "UsedCarsPriceChart/FillVersionModel/",
data: { id: $(this).val() },
cache: false,
type: "POST",
dataType: "JSON",
success: function (models) {
$("#VersionModelId").html(""); // clear before appending new list
$("#VersionModelId").append(
$('<option></option>').html("--Wybierz-Wersję--"));
$.each(models, function (i, model) {
$("#VersionModelId").append(
$('<option></option>').val(model.VersionModelId).html(model.VersionModelName));
});
}
});
});
Problem
TestCase #1: When page is loaded and I click submit button then validation messages are not displayed.
TestCase #2: When page is loaded and I select option "--Wybierz-Markę--" then the validation messages is not displayed (I suspect to be).
TestCase #3: When page is loaded and I select first option and next option "--Wybierz-Markę--" then the validation messages is not displayed (I suspect to be).
Problem source:
I figure out that this peace of script brokes this functionality (when I comment this my code works well):
$('#SearchCarsOffers').click(function () {
var form = $("#SearchCarsOffersForm");
var url = form.attr("action");
var formData = form.serialize();
$.post(url, formData, function (data) {
areaChart.setData(data);
//CreateMorrisArea(data);
});
return false;
});
Please explain me where exactly is the problem and how can I fix it?

Replace dropdown selection with table

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>

How to create a List<> hidden field and submit it to controller in MVC

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.

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