How do i send the data to edit boxes on the same page? - asp.net-mvc-4

i have the following page generated
when i click the Edit link, the record data must be sent to the input boxes on teh same page (without refreshing the page)
currently i have the controller code and views
controller: ProductCategory
public class ProductCategoryController : Controller
{
//
// GET: /ProductCategory/
TUDBEntities _db = new TUDBEntities();
public ActionResult Index(string Code)
{
var categories = _db.mt_ProductCategories
.Where(pc => Code == "" || Code == null|| pc.CatCode == Code)
.Select(
c => new ProductCategory {
Id = c.Id,
CategoryCode = c.CatCode,
Name = c.CatName,
Url = c.Url
});
if (Request.IsAjaxRequest())
{
return PartialView("_ProductCategoryList", categories);
}
return View(categories);
}
[HttpPost]
public ActionResult Save(ProductCategory userData)
{
try
{
if (ModelState.IsValid)
{
mt_ProductCategories cat = new mt_ProductCategories { CatCode = userData.CategoryCode, CatName = userData.Name };
// TODO: Add insert logic here
_db.mt_ProductCategories.Add(cat);
_db.SaveChanges();
return RedirectToAction("Index");
}
return View();
}
catch
{
return View();
}
}
public ActionResult Edit(int id)
{
var category = _db.mt_ProductCategories
.Where(pc => pc.Id == id)
.Select(pc => new ProductCategory
{ Id=pc.Id, CategoryCode=pc.CatCode,Name=pc.CatName }).ToList();
return RedirectToAction("Index", category);
}
}
Index view
#model IEnumerable<eComm1.Models.ProductCategory>
#using(Ajax.BeginForm("Save", "ProductCategory",
new AjaxOptions {
HttpMethod="POST",
UpdateTargetId="prod-grid",
InsertionMode=InsertionMode.Replace,
OnSuccess="loaddivdata"
}))
{
<fieldset class="form-group">
<label for="Code">Category Code</label>
<input type="text" class="form-control focus" id="Code" name="CategoryCode" placeholder="Product category code" >
</fieldset>
<fieldset class="form-group">
<label for="ProdName">Product Name</label>
<input type="text" class="form-control" id="ProdName" name="Name" placeholder="Product Name">
</fieldset>
<button type="Submit" class="btn btn-primary">Save</button>
}
<hr />
<div id="prod-grid">
#Html.Partial("_ProductCategoryList", #Model)
</div>
<script type="text/javascript">
$(function () {
$('.focus :input').focus();
});
function loaddivdata() {
$('#prod-grid').html();
$('#Code, #ProdName').val('');
};
// $('#prod-grid').load(function () {
// $.ajax({
// url:'ProductCategoryController/Index',
// method:'GET',
// type:'application/html',
// success: function () { alert('called');}
// });
// });
//});
</script>
Partial View: _ProductCategoryList
#model IEnumerable<eComm1.Models.ProductCategory>
<div class="panel panel-default">
#if (Model.Count() == 0) { <div class="panel-heading">Product Categories - <span style='color:red;font-weight:bold' >0 RESULTS FOUND</span></div>
}else{
<!-- Default panel contents -->
<div class="panel-heading">Product Categories</div>
}
<!-- Table -->
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.CategoryCode)
</th>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Url)
</th>
<th></th>
</tr>
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.CategoryCode)
</td>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Url)
</td>
<td>
#*#Html.beActionLink("Edit", "Edit", new { id=item.Id }) |
#Html.ActionLink("Details", "Details", new { id=item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id=item.Id })*#
#Ajax.ActionLink("Edit", "Edit", "ProductCategory", new { id=item.Id}, new AjaxOptions { HttpMethod = "GET", UpdateTargetId = "", OnSuccess = "loadformdata" }) |
#Ajax.ActionLink("Delete", "Delete", "ProductCategory", new { id=item.Id}, new AjaxOptions{ HttpMethod="POST", UpdateTargetId="", OnSuccess="loadformdata"})
</td>
</tr>
}
</table>
</div>
How do i modify my code to send data those input control and in my code how do i create hidden field for Id value so it can be send to the Edit(collection, int id) action to update the record?
for Stephen Muecke, i have added my jquery files through the bundles
public static void RegisterBundles(BundleCollection bundles)
{
bundles.Add(new ScriptBundle("~/bundles/ecomm").Include(
"~/Scripts/jquery-{version}.js",
"~/Scripts/jquery-2.1.4.min.js",
"~/Scripts/bootstrap.js",
"~/Scripts/bootstrap.min.js",
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*"
));
bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
"~/Scripts/jquery-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryui").Include(
"~/Scripts/jquery-ui-{version}.js"));
bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
"~/Scripts/jquery.unobtrusive*",
"~/Scripts/jquery.validate*"));
// Use the development version of Modernizr to develop with and learn from. Then, when you're
// ready for production, use the build tool at http://modernizr.com to pick only the tests you need.
bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
"~/Scripts/modernizr-*"));
bundles.Add(new StyleBundle("~/Content/css").Include(
"~/Content/bootstrap.min.css",
"~/Content/bootstrap.css",
"~/Content/style.css"));
bundles.Add(new StyleBundle("~/Content/themes/base/css").Include(
"~/Content/themes/base/jquery.ui.core.css",
"~/Content/themes/base/jquery.ui.resizable.css",
"~/Content/themes/base/jquery.ui.selectable.css",
"~/Content/themes/base/jquery.ui.accordion.css",
"~/Content/themes/base/jquery.ui.autocomplete.css",
"~/Content/themes/base/jquery.ui.button.css",
"~/Content/themes/base/jquery.ui.dialog.css",
"~/Content/themes/base/jquery.ui.slider.css",
"~/Content/themes/base/jquery.ui.tabs.css",
"~/Content/themes/base/jquery.ui.datepicker.css",
"~/Content/themes/base/jquery.ui.progressbar.css",
"~/Content/themes/base/jquery.ui.theme.css"));
}
In the partial view
#Ajax.ActionLink("Edit", "Edit", "ProductCategory", new { id = item.Id }, new AjaxOptions { HttpMethod = "GET", OnSuccess = "loadformdata" }) |
#Ajax.ActionLink("Delete", "Delete", "ProductCategory", new { id=item.Id}, new AjaxOptions{ HttpMethod="POST", OnSuccess="loadformdata"})
in the index view the following js function:
function loadformdata() {
var cells = $(this).closest('tr').children('td');
alert(cells.eq(0).text());
//$('#Code').val(cells.eq(0).text());
//$('#ProdName').val(cells.eq(1).text());
}
To: Stephen Muecke:
i have removed above loadformdata() and put everything as you said. this youtube video shows the problem that still does not call that click event
To: Steven Meucke:
there's still no luck, for ease i have added a alert() in the function and the alert() won't show. Here is the video

Give you 'Edit' link a class name (say) class="edit" and handle its .click() event to update the form controls
$('.edit').click(function() {
var cells = $(this).closest('tr').children('td');
$('#Code').val(cells.eq(0).text());
$('#ProdName').val(cells.eq(1).text());
return false; // cancel the default redirect
});
Side note: You could just replace the ActionLink() code with Edit and the return false; line is not necessary.

write script for ajax call:
$('#edit').click(function () {
// var data = {here you will pass item.id }
$.ajax({
url:'ProductCategoryController/Edit',
data: {id : data}
method:'GET',
success: function (data) {
//clear html page here
//reload html page by passing 'data' passes in function
//e.g. suppose html page id is 'xyz' then $("#xyz").html(data)
}
});
});

Related

Razor Page Cascade Datatables by Dropdown

i have a dropdown list and i want to reload the datatable once i change the dropdown please note that the Checkbox field postback the page as well to update the database below is the cs file and will post the cshtml after
public class IndexModel : PageModel
{
private readonly IpponAcademy.Models.IJAContext _context;
public List<SelectListItem> judokaGroupList { get; set; }
[BindProperty]
public Boolean IsAttend { get; set; }
public IList<tbl_Judoka> tbl_Judoka { get;set; }
public IndexModel(IpponAcademy.Models.IJAContext context)
{
_context = context;
}
public void OnGet(Guid? id)
{
var GroupList = _context.LK_Groups.ToList();
judokaGroupList = GroupList.Select(a =>
new SelectListItem
{
Value = a.Group_GUID.ToString(),
Text = a.Group_Name
}).OrderBy(t => t.Text).ToList();
if (id == null)
{
id = Guid.Parse("7F299B82-3397-40F2-8105-65AECB1BA2A8"); //Group A
}
tbl_Judoka = _context.tbl_Judokas.Where(c => c.tbl_Judoka_Groups.Any(o => o.Is_Active == true && o.Group_GUID == id)).Include(c => c.tbl_Judoka_Groups.Where(o => o.Is_Active == true && o.Group_GUID == id)).ToList();
}
public void OnGetJudoka(Guid? id)
{
var GroupList = _context.LK_Groups.ToList();
judokaGroupList = GroupList.Select(a =>
new SelectListItem
{
Value = a.Group_GUID.ToString(),
Text = a.Group_Name
}).OrderBy(t => t.Text).ToList();
if (id == null)
{
id = Guid.Parse("7F299B82-3397-40F2-8105-65AECB1BA2A8"); //Group A
}
tbl_Judoka = _context.tbl_Judokas.Where(c => c.tbl_Judoka_Groups.Any(o => o.Is_Active == true && o.Group_GUID == id)).Include(c => c.tbl_Judoka_Groups.Where(o => o.Is_Active == true && o.Group_GUID == id)).ToList();
}
}
below is the cshtml file, I'd appreciate finding a simple way to do refresh the datatable with the new selected field from the dropdown
#page
#model IpponAcademy.Pages.Attendance.IndexModel
#{
ViewData["Title"] = "Index";
Layout = "~/Pages/Shared/_Layout.cshtml";
}
<form method="post">
<div class="form-group">
<label class="control-label">Group</label>
<select id="ddlGroup" class="form-control" asp-items="Model.judokaGroupList"></select>
#*onchange="alert(#Model.judokaGroupList)"*#
</div>
<table id="taskDt" class="table table-striped table-bordered nowrap" style="width:100%">
<thead>
<tr>
<th>
Attended
</th>
<th>
Code
</th>
<th>
Image
</th>
<th>
Judoka
</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.tbl_Judoka)
{
var imagePath = #"UploadedFiles/" + item.J_Image;
<tr>
<td>
<input type="hidden" name="J_GUID" id="J_GUID" value="#item.J_GUID" />
<input asp-for="IsAttend" name="IsAttend" id="IsAttend" type="checkbox" onclick="this.form.submit()" />
</td>
<td>
#Html.DisplayFor(modelItem => item.J_Code)
</td>
<td align="center">
<img src="#imagePath" alt="Judoka" width="50" height="50" class="rounded-circle mr-1" onclick="return LoadDiv(this.src);" style="cursor: pointer" />
</td>
<td>
#Html.DisplayFor(modelItem => item.J_Name)
</td>
</tr>
}
</tbody>
</table>
</form>
#section scripts{
<script>
var table;
function LoadData() {
//alert('in');
table = $("#taskDt").DataTable({
paging: true,
responsive: true,
searching: true,
ordering: true,
order: [[1, "asc"]]
});
}
$(document).ready(function () {
LoadData();
})
$("#ddlGroup").change(function () {
alert('DDLGroup');
//table.ajax.url("/Attendance/Index?handler=GET&Id=" + $("#ddlGroup Option:Selected").val()).load();
window.location.href = '/Attendance/Index?handler=Judoka&Id=' + $("#ddlGroup Option:Selected").val();
});
</script>
}
I think using window.location.href has been a simple enough way to refresh the datatable.Just one thing you need to improve,OnGet and OnGetJudoka method are the same,why not using just one method.
Change:
$("#ddlGroup").change(function () {
window.location.href = '/Attendance/Index?handler=Judoka&Id=' + $("#ddlGroup Option:Selected").val();
});
To:
$("#ddlGroup").change(function () {
window.location.href = '/Attendance/Index?Id=' + $("#ddlGroup Option:Selected").val();
});
If you must use another way,you could use form.submit():
Note:Be sure add name="id",otherwise the parameter will not bind to the backend.
<form method="post" asp-page-handler="Judoka">
<div class="form-group">
<label class="control-label">Group</label>
<select id="ddlGroup" class="form-control" name="id" asp-items="Model.judokaGroupList"
onchange="this.form.submit()"></select>
</div>
</form>
Backend code:
Change:
public void OnGetJudoka(Guid? id)
To:
public void OnPostJudoka(Guid? id)
BTW,I also have a suggestion that you'd better maintain the selected value after postback :
public void OnGetJudoka(Guid? id)
{
var GroupList = _context.LK_Groups.ToList();
judokaGroupList = GroupList.Select(a =>
new SelectListItem
{
Value = a.Group_GUID.ToString(),
Text = a.Group_Name
}).OrderBy(t => t.Text).ToList();
//maintain the selected value after post back...
var selectedValue= judokaGroupList.Where(a => a.Value == id.ToString()).FirstOrDefault();
selectedValue.Selected = true;
//...
}

How to get My Purchase Class to map to a Specific ApplicationUser

Here is the Idea. When an Admin is logged on they can pull up a list of all of the users.It will give the options for edit, details, delete like normal but I have added a link to Purchases like so:
#model IEnumerable<IdentitySample.Models.ApplicationUser>
#{
ViewBag.Title = "Index";
}
<div class="col-12 backgroundImg">
<div class="navbarSpace">
<div class="col-12 formBackground">
<h2 class="formHeader">List of Users</h2>
<h4 class="formText">
#Html.ActionLink("Create New ", "Create")
</h4>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Email)
</th>
<th>
#Html.DisplayNameFor(model => model.UserName)
</th>
<th>
#Html.DisplayNameFor(model => model.FavStrain)
</th>
<th>
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Email)
</td>
<td>
#Html.DisplayFor(modelItem => item.UserName)
</td>
<td>
#Html.DisplayFor(modelItem => item.FavStrain)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id
}) |
#Html.ActionLink("Details", "Details", new { id =
item.Id }) |
#Html.ActionLink("Delete", "Delete", new { id =
item.Id }) |
#Html.ActionLink("Purchases", "PurchaseIndex", new {
id = item.Id})
</td>
</tr>
}
</table>
</div>
</div>
</div>enter code here
When you click the Purchases link it takes you to the PurchaseIndex page which looks like this:
Purchase List
#model IEnumerable<IdentitySample.Models.Purchases>
#{
ViewBag.Title = "Index";
}
<div class="col-12 backgroundImg navbarSpace">
<div class="col-12 formBackground">
<h2 class="formHeader">Index</h2>
<hr />
<div class="formHeaderSmall">
Total Points <br />
#Model.Sum(i => i.Points) </div>
<p class="formText">
#Html.ActionLink("Create New", "CreatePurchase")
</p>
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Points)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.Points)
</td>
<td></td>
</tr>
}
</table>
<p class="formText">
#Html.ActionLink("Back to List", "Index")
</p>
</div>
</div>
It gives a list of Purchases and gives the total points that is why i didnt include a details page. Everything works right EXCEPT for the fact that the Purchases do not map to a specific user. If I create a new user and click Purchases it brings up a list of all of the purchases, not just the purchases specific for that user. How do I get a Purchase to map to a Specific User?
I have created a Purchases class that looks like this:
public class Purchases
{
[Key]
public int PurchaseId { get; set; }
[Required]
[Display(Name = "Product Name")]
[DataType(DataType.Text)]
public string Name { get; set; }
[Required]
[Range(0,5)]
[Display(Name = "Points")]
[DataType(DataType.Text)]
public int Points { get; set; }
public string ApplicationUserId { get; set; }
public virtual ApplicationUser Users { get; set; }
}
My ApplicationUser Class looks like this:
public class ApplicationUser : IdentityUser
{
[Display(Name ="Favorite Strain")]
[DataType(DataType.Text)]
public string FavStrain { get; set; }
public virtual List<Purchases> Purchase { get; set; }
Now up to this point the Database is registering the Foreign Key of the Purchases Class to the ApplicationUser class like it is supposed to.
I can create a new purchase and display them to a list and all of the Crud Operations work just fine.
The problem is when I create a new Purchase it doesn't include the ApplicationUserId in the Database it returns a Null.
Null Database
I am pretty sure that the problem is in my Controller. I have tried just about everything so I don't want to include the failed try's so here is the Controllers as they are now and working.
There is no need for me to include the edit or details because I am not going to give the users that access.
public ActionResult CreatePurchase()
{
return View();
}
private ApplicationDbContext db = new ApplicationDbContext();
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreatePurchase([Bind(Include = "PurchaseId,Name,Points,Id")] Purchases purchases)
{
if (ModelState.IsValid)
{
db.Purchases.Add(purchases);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(purchases);
}
// GET: Purchases/Edit/5
public ActionResult PurchaseIndex()
{
var userDetails = db.Purchases.Include(u => u.Users);
return View(db.Purchases.ToList());
}
This is my first Question on Stack Overflow so forgive me if something isn't right.
**************************************Update************************************
This is my PurchaseIndexController. Now this returns only the user associated with the purchase. However it is always 0 because there is no UserID. If I try using an int? type or Guid? it gives an error. Cannot implicitly convert type int to string.
public ActionResult PurchaseIndex(string ID)
{
//this gets all purchases for a certain individual
ApplicationDbContext db = new ApplicationDbContext();
var userDetails = db.Purchases.Where(x => x.ApplicationUserId ==
ID).ToList();
return View(userDetails);
}
Here is the CreatePurchase View
#model IdentitySample.Models.Purchases
#{
ViewBag.Title = "Create";
}
<div class="col-12 backgroundImg navbarSpace">
<div class="col-12 formBackground">
<h2 class="formHeader">Add a New Purchase</h2>
<hr />
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#*#Html.Hidden("id", (string)ViewBag.UserID)*#
#Html.HiddenFor(model => model.ApplicationUserId)
<div class="form-horizontal">
<div class="col-12">
#Html.LabelFor(model => model.Name, htmlAttributes: new {
#class = "formText col-12" })
<div class="col-md-10">
#Html.EditorFor(model => model.Name, new {
htmlAttributes = new { #class = "col-12" } })
#Html.ValidationMessageFor(model => model.Name, "", new
{ #class = "text-danger" })
</div>
</div>
<div class="col-12">
#Html.LabelFor(model => model.Points, htmlAttributes: new {
#class = "formText col-12" })
<div class="col-md-10">
#Html.EditorFor(model => model.Points, new {
htmlAttributes = new { #class = "col-12" } })
#Html.ValidationMessageFor(model => model.Points, "",
new { #class = "text-danger" })
</div>
</div>
<div class="col-12">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-
default" />
</div>
</div>
</div>
}
<div class="formText">
#Html.ActionLink("Back to List", "Index")
</div>
</div>
</div>
I also have link in the Manage section for the users to check thier points and purchases but I don't know how to create an ActionLink for this to just get purchases associated with the user.
Here is the controller
public ActionResult WeedPoints(string ID)
{
ApplicationDbContext db = new ApplicationDbContext();
var userDetails = db.Purchases.Where(x => x.ApplicationUserId ==
ID).ToList();
return View(userDetails);
}
Here is the Action Link now.
<div class="col-12 formHeaderSmall">#Html.ActionLink("My
Purchases/Points", "WeedPoints", "Manage")</div>
*********************************Update****************************************
Here is the Controllers with the View Bag reference. The Create Purchase View has the ViewBag I just Uncommented it out.
[Authorize(Roles =
"Admin,DispensaryManager,DispensaryEmployee,DispensaryEastEmployee")]
public ActionResult CreatePurchase(string Id)
{
ViewBag.UserID = Id;
//ApplicationDbContext db = new ApplicationDbContext();
//var userDetails = db.Purchases.Where(x => x.ApplicationUserId == Id;
return View();
}
private ApplicationDbContext db = new ApplicationDbContext();
//POST: Purchases/Create
[HttpPost]
[Authorize(Roles =
"Admin,DispensaryManager,DispensaryEmployee,DispensaryEastEmployee")]
[ValidateAntiForgeryToken]
public ActionResult CreatePurchase([Bind(Include =
"PurchaseId,Name,Points,ApplicationUserId")] Purchases
purchases,string id)
{
if (ModelState.IsValid)
{
db.Purchases.Add(purchases);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(purchases);
}
[Authorize(Roles =
"Admin,DispensaryManager,DispensaryEmployee,DispensaryEastEmployee")]
public ActionResult PurchaseIndex(string Id)
{
//this gets all purchases for a certain individual
ApplicationDbContext db = new ApplicationDbContext();
var userDetails = db.Purchases.Where(x => x.ApplicationUserId ==
Id).ToList();
ViewBag.UserID = Id;
return View(userDetails);
}
***************************Total Refactor*********************************8
Here is the new controller in its entirety.
public class PurchasesController : Controller
{
private ApplicationDbContext db = new ApplicationDbContext();
// GET: Purchases
public ActionResult Index()
{
var purchases = db.Purchases.Include(p => p.Users);
return View(purchases.ToList());
}
// GET: Purchases/Details/5
public ActionResult Details(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Purchases purchases = db.Purchases.Find(id);
if (purchases == null)
{
return HttpNotFound();
}
return View(purchases);
}
// GET: Purchases/Create
public ActionResult Create()
{
ViewBag.Users = new SelectList(db.Users, "Id", "UserName");
List<SelectListItem> selectListItems = new List<SelectListItem>();
foreach (ApplicationUser user in db.Users)
{
SelectListItem selectListItem = new SelectListItem
{
Text = user.UserName,
Value = user.Id.ToString()
};
selectListItems.Add(selectListItem);
}
//ViewBag.ApplicationUserId = new SelectList(db.Users, "Id",
"UserName");
return View();
}
// POST: Purchases/Create
// To protect from overposting attacks, please enable the specific
properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include =
"PurchaseId,Name,Points,TotalPoints,ApplicationUserId")] Purchases
purchases)
{
if (ModelState.IsValid)
{
var totalPoints = db.Purchases.Sum(x => x.Points);
purchases.TotalPoints = totalPoints;
db.Purchases.Add(purchases);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ApplicationUserId = new SelectList(db.Users, "Id",
"UserName", purchases.ApplicationUserId);
return View(purchases);
}
// GET: Purchases/Edit/5
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Purchases purchases = db.Purchases.Find(id);
if (purchases == null)
{
return HttpNotFound();
}
ViewBag.ApplicationUserId = new SelectList(db.Users, "Id",
"UserName", purchases.ApplicationUserId);
return View(purchases);
}
// POST: Purchases/Edit/5
// To protect from overposting attacks, please enable the specific
properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include =
"PurchaseId,Name,Points,TotalPoints,ApplicationUserId")] Purchases
purchases)
{
if (ModelState.IsValid)
{
db.Entry(purchases).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.ApplicationUserId = new SelectList(db.Users, "Id",
"UserName", purchases.ApplicationUserId);
return View(purchases);
}
// GET: Purchases/Delete/5
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Purchases purchases = db.Purchases.Find(id);
if (purchases == null)
{
return HttpNotFound();
}
return View(purchases);
}
// POST: Purchases/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Purchases purchases = db.Purchases.Find(id);
db.Purchases.Remove(purchases);
db.SaveChanges();
return RedirectToAction("Index");
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
db.Dispose();
}
base.Dispose(disposing);
}
}
}
Now there is a dropdown list of users to choose from when you create a new purchase. Here is the Create View.
<div class="col-12 backgroundImg navbarSpace scrollBar">
<div class="formBackground col-12">
<h2 class="formHeader">Edit Puchase</h2>
<hr />
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
#Html.ValidationSummary(true, "", new { #class = "text-danger"
})
#Html.HiddenFor(model => model.PurchaseId)
#Html.HiddenFor(model => model.TotalPoints)
<div class="col-12">
#Html.LabelFor(model => model.Name, htmlAttributes: new {
#class = "formText col-12" })
<div class="col-12">
#Html.EditorFor(model => model.Name, new {
htmlAttributes = new { #class = "col-12" } })
#Html.ValidationMessageFor(model => model.Name, "", new
{ #class = "text-danger" })
</div>
</div>
<div class="col-12">
#Html.LabelFor(model => model.Points, htmlAttributes: new {
#class = "formText col-12" })
<div class="col-12">
#Html.EditorFor(model => model.Points, new {
htmlAttributes = new { #class = "col-12" } })
#Html.ValidationMessageFor(model => model.Points, "",
new { #class = "text-danger" })
</div>
</div>
#*<div class="col-12">
#Html.LabelFor(model => model.TotalPoints,
htmlAttributes: new { #class = "formText col-12" })
<div class="col-12">
#Html.EditorFor(model => model.TotalPoints, new {
htmlAttributes = new { #class = "col-12" } })
#Html.ValidationMessageFor(model =>
model.TotalPoints, "", new { #class = "text-danger" })
</div>
</div>*#
<div class="col-12">#Html.LabelFor(model => model.ApplicationUserId,
"Users", htmlAttributes: new { #class = "formText col-12" })
<div class="col-12"> #Html.DropDownList("Users", null, htmlAttributes:
new { #class = "col-12" })
#Html.ValidationMessageFor(model => model.ApplicationUserId, "", new {
#class = "text-danger" })
</div>
</div>
<div class="col-12">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div class="formText"> #Html.ActionLink("Back to List", "Index")
</div>
</div>
</div>
This creates a drop down list of users displaying their User Name. When I select a user and hit save I get an error saying that
There is no ViewData item of type 'IEnumerable' that has the key 'Id'.
Is the 'Id' being passed to this method null?
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CreatePurchase([Bind(Include = "PurchaseId,Name,Points,Id")] Purchases purchases)
{
if (ModelState.IsValid)
{
db.Purchases.Add(purchases);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(purchases);
}
If it is null, the userID should be included (as a hidden field) in the form you are posting. Then (once the userID is populated in the DB) you should be able to get only the purchase associated with the userID, doing something like this:
var userDetails = db.Purchases.Where(x=>x.ApplicationUserId == ID).ToList();
The problem you are having is that the 'Create new purchase' action is not passing a user id, it is currently:
#Html.ActionLink("Create New", "CreatePurchase")
Whereas it needs to be this to pass an id:
#Html.ActionLink("Create New", "CreatePurchase", new {
id = Model.Id})
However this assumes that an id has been passed to the purchase index view in the model for that page, which is likely not the case but I can't tell as I can't see your purchase index action. The simplest way to pass it for you is through a viewbag, however I do not recommend using this for your site if you intend to use it seriously. The correct way to handle data across your views would be using viewmodels. There are a lot of tutorials available, e.g. https://learn.microsoft.com/en-us/aspnet/mvc/overview/older-versions/mvc-music-store/mvc-music-store-part-3
Using the CRUD implementation you have you can just pass the id to the page using a weakly type viewbag. Your purchase index action should look something like this:
public ActionResult Index(string id)
{
//this checks to see if an id has been passed to the action
if (id != null){
//this gets all purchases for a certain individual
var purchases = db.purchases.Where(i => i.ApplicationUserId == id).ToList()
//this gets the user id passed to the action and sticks it in a viewbag you can retrieve later on the page
ViewBag.UserID == id;
//returns the view with the list above
return View(purchases);
}
else{
//no id was passed to the action so the list is for all purchases
var purchases = db.purchases.ToList();
return View(purchases);
}
}
Now in your view you need to amend the create new purchase action to include the viewbag item:
#Html.ActionLink("Create New", "CreatePurchase", new {
id = ViewBag.UserID})
Change your create purchase action to accept the user id you are passing:
public ActionResult CreatePurchase(string id)
{
//puts the id in a viewbag to again be used by the view
ViewBag.UserID == id;
return View();
}
Then on your create purchase view you need to pass the viewbag item into the model, you do this by having a hidden field somewhere inside the form:
#Html.Hidden("id", (string)ViewBag.UserID)
I'm converting the viewbag into a string because assuming your are using ASP NET identity the user id is a string and ViewBag is a dynamic object, so needs to be turned into a string before you can put it into the model.id space effectively. This will then pass the user ID to the post action and a purchase will be created specific to the id.
Bear in mind, this is a terrible way to be doing this, the default CRUD stuff whilst handy isn't really that great for production because you are accessing models directly and you will need to use weakly typed ViewBags to transfer data. It's error prone and insecure.

Dynamic partial view list not being picked up when saving

I have a partial view that gets rendered in my main view using this code
<div>
<h3>Budget Detail</h3>
<div><input type="button" id="addbudgetdetail" value="Add row" /></div>
<div id="new-budgetdetail">
#if (Model.budget != null)
{
foreach (var budgetdetail in Model.budget.budgetdetails)
{
#Html.Partial("budgetdetail", Model)
}
}
else
{
#Html.Partial("budgetdetail", Model)
}
</div>
</div>
There is a java script to dynamically add more partial views when clicking a button
$(function () {
$('#addbudgetdetail').on('click', function () {
jQuery.get('#Url.Action("budgetdetail")').done(function (html) {
$('#new-budgetdetail').append(html);
$('form').data('validator', null);
$.validator.unobtrusive.parse($('form'));
});
});
});
This is My partial view:
#model BudgetPortalMVC4.Models.NewBudgetModel
#{
Layout = null;
}
<script src="../../Scripts/jquery.validate.js" type="text/javascript"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js" type="text/javascript"></script>
#using (Html.BeginCollectionItem(""))
{
#Html.ValidationSummary(true)
<div class="item">
<table>
<tr>
<td>
#Html.LabelFor(m => m.SelectedCategory)
#Html.DropDownListFor(m => m.SelectedCategory, Model.CategoriesList, "Please select", new { #class = "SelectedCategory" })
#Html.ValidationMessageFor(m => m.SelectedCategory)
</td>
<td>
#Html.LabelFor(m => m.SelectedSubCategory)
#Html.DropDownListFor(m => m.SelectedSubCategory, Model.SubCategoriesList, "Please select", new { #class = "SelectedSubCategory" })
#Html.ValidationMessageFor(m => m.SelectedSubCategory)
</td>
<td>
#Html.LabelFor(model => model.budgetdetail.Amount)
#Html.EditorFor(model => model.budgetdetail.Amount)
#Html.ValidationMessageFor(model => model.budgetdetail.Amount)
</td>
<td><a href="#" id="deleteRow" class="deleteRow">Delete</a</td>
</tr>
</table>
</div>
}
My problem is when I click submit I don't see any list for my partial views.
I can only see the data that is coming directly from my main view.
Am I missing an IEnumerable property somewhere? Should I try to use editor templates instead?
I have solved this problem. I had to rework the models and instead of using a big model grouping the budget and budget detail, i am using 2 models one for budget and one for budgetdetail.
I also rewrote the dropdown lists to comply to the new model:
#Html.LabelFor(m => m.category)
#Html.DropDownListFor(m => m.idCategory, new SelectList(ViewBag.CategoriesList, "idCategory", "CategoryName"), "Please select", new { #class = "SelectedCategory" })
#Html.ValidationMessageFor(m => m.idCategory)
#Html.LabelFor(m => m.subcategory)
#Html.DropDownListFor(m => m.idSubcategory, new SelectList(ViewBag.SubCategoriesList, "Value", "Text"), "Please select", new { #class = "SelectedSubCategory" })
#Html.ValidationMessageFor(m => m.idSubcategory)
Instead of using a partial view I created an editor template. Following Stephen's advice that BeginCollection can't be use in editor templates I used an html helper to create unique items in my collection.
This is the code for the helper:
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;
namespace BudgetPortalMVC4.Extensions
{
public static class HtmlHelperExtensions
{
public static MvcHtmlString EditorForMany<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, IEnumerable<TValue>>> expression, string htmlFieldName = null) where TModel : class
{
var items = expression.Compile()(html.ViewData.Model);
var sb = new StringBuilder();
if (String.IsNullOrEmpty(htmlFieldName))
{
var prefix = html.ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix;
htmlFieldName = (prefix.Length > 0 ? (prefix + ".") : String.Empty) + ExpressionHelper.GetExpressionText(expression);
}
foreach (var item in items)
{
var dummy = new { Item = item };
var guid = Guid.NewGuid().ToString();
var memberExp = Expression.MakeMemberAccess(Expression.Constant(dummy), dummy.GetType().GetProperty("Item"));
var singleItemExp = Expression.Lambda<Func<TModel, TValue>>(memberExp, expression.Parameters);
sb.Append(String.Format(#"<input type=""hidden"" name=""{0}.Index"" value=""{1}"" />", htmlFieldName, guid));
sb.Append(html.EditorFor(singleItemExp, null, String.Format("{0}[{1}]", htmlFieldName, guid)));
}
return new MvcHtmlString(sb.ToString());
}
}
}
Now I call the template from the main using an intermediate IEnumerable view.
This is the call from the main view:
#Html.EditorForMany(x => x.budgetdetails)
And this is the intermediate IEnumerable view:
#model IEnumerable<BudgetPortalMVC4.Models.budgetdetail>
#{
Layout = null;
}
#Html.EditorForMany(x => x, "budgetdetails")
Hope this is helpful.

why delete failed to removed the record?

i have the following action for the delete operation:
[HttpPost]
public ActionResult Delete(int id, EmployeeDeleteViewModel collection)
{
try
{
if (ModelState.IsValid)
{
Employee e = new Employee
{
EmpId = collection.EmpID,
FirstName = collection.FirstName,
LastName = collection.LastName,
DepartmentId = collection.DepartmentID
};
db.Employees.Remove(e);
db.SaveChanges();
return RedirectToAction("Index", new { id = id });
}
// TODO: Add update logic here
return View(collection);
}
catch
{
return View();
}
}
the delete view is:
#model VirtualCampus2.Models.EmployeeDeleteViewModel
#{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<fieldset>
<legend>EmployeeDeleteViewModel</legend>
<div class="display-label">
#Html.DisplayNameFor(model => model.EmpID)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.EmpID)
</div>
<div class="display-label">
#Html.DisplayNameFor(model => model.FirstName)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.FirstName)
</div>
<div class="display-label">
#Html.DisplayNameFor(model => model.LastName)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.LastName)
</div>
<div class="display-label">
#Html.DisplayNameFor(model => model.DepartmentName)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.DepartmentName)
</div>
<div class="display-label">
#Html.DisplayNameFor(model => model.DepartmentID)
</div>
<div class="display-field">
#Html.DisplayFor(model => model.DepartmentID)
</div>
</fieldset>
#using (Html.BeginForm())
{
<p>
<input type="submit" value="Delete" /> |
#Html.ActionLink("Back to List", "Index")
</p>
}
When i click delete on the delete view the following error occurs:
to action method's second parameter "collection", it sends a collection with zeros and null in their properties
does not delete the record
Here is video that shows the problem
why this happens and how do i fix this?
To Steve:
I have made the changes by creating a separate view model and a delete action:
ViewModel:
public class EmpDeleteCommitViewModel
{
public int EmpID { get; set; }
}
the actions Delete methods:
[HttpGet]//this works fine, gets the record to show on view
public ActionResult Delete(int id)
{
var empList = db.Employees.ToList();
var employee = empList.Where(e => e.EmpId == id).Select(e => new EmployeeDeleteViewModel
{
EmpID=e.EmpId,
FirstName= e.FirstName,
LastName=e.LastName,
DepartmentID=e.DepartmentId,
DepartmentName=e.Department.Name
}).FirstOrDefault();
return View(employee);
}
[HttpPost] //BUT THIS DOES NOT WORK!, evm EmpID does not contain id value
public ActionResult Delete(EmpDeleteCommitViewModel evm)
{
try
{
var employee = db.Employees.Where(e => e.EmpId == evm.EmpID).FirstOrDefault();
db.Employees.Remove(employee);
db.SaveChanges();
return RedirectToAction("Index", new { id = evm.EmpID });
}
catch
{
return View();
}
}
You have no form controls (<input>) between form tags so there is nothing to post back when you submit the form. All you doing is generating some text to display the property values.
There is no need to include the EmployeeDeleteViewModel collection parameter in you method. Your int id parameter will be bound with the id of the employee assuming your using the correct routing, so all you need to to get the original data model from the database based on the id and then delete it.

POSt method for AJAX dynamically created input fields

I am using MVC and razor.
I have sucessfully implemented a way for the user to dynamically add more input rows at a click of a button (using AJAX and following Steven Sanderson's blog). BUT I do not know how to save the data to a database that the user inputs in these dynamically created fields.
I use his helper class, that quite frankly I am struggling to understand at all.
My question is what do I need to put in the POST create method. The link to the code in his blog is here:
steven sanderson's blog
Just a pointer in the right direction is all I need. THis is my current code:
New row partial view:
#model ef_tut.ViewModels.ClaimsViewModel
#using ef_tut.WebUI.Helpers
#using (Html.BeginCollectionItem("claims"))
{
<table class="editorRow">
<tr >
<td>
SubmissionUserID: #Html. EditorFor (o.claim.SubmissionUserID)
</td>
<td>
ClaimID: #Html.EditorFor(o => o.claim.ClaimID)
</td>
<td>
#Html.EditorFor(o => o.claim.ApprovedYN)
</td>
<td>
ClaimID(claimlinetable)#Html.EditorFor(o => o.claimline.ClaimID)
</td>
<td>
ClaimantUserID: #Html.EditorFor(o => o.claimline.ClaimantUserID)
</td>
<td>
Hours: #Html.EditorFor(o => o.claimline.Hours)
</td>
<td>
MileageCost: #Html.EditorFor(o => o.claimline.MileageCost)
</td>
<td>
TravelCost: #Html.EditorFor(o => o.claimline.TravelCost)
</td>
<td>
Hours cost: #Html.EditorFor(o => o.claimline.HoursCost)
</td>
<td>
Total cost: #Html.EditorFor(o => o.claimline.TotalCost)
</td>
<td>
ProxyYN: #Html.EditorFor(o => o.claimline.ProxyClaim)
</td>
<td>
CatID: #Html.EditorFor(o => o.claimline.CatID)
</td>
<td>
SubCatID: #Html.EditorFor(o => o.claimline.SubCatID)
</td>
<td>
delete
</td>
</tr></table>
}
Blankeditorrowmethod
public PartialViewResult BlankEditorRow()
{
return PartialView("NewRow", new ClaimsViewModel());
}
My current POST method that creates new DB records but all fields are null
[HttpPost]
public ActionResult Create(ClaimsViewModel viewModel)
{
if (ModelState.IsValid)
{
db.claims.Add(viewModel.claim);
db.claimlines.Add(viewModel.claimline);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(viewModel);
}
Create view
#model ef_tut.ViewModels.ClaimsViewModel
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>claim</legend>
<div id="editorRows"></div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Add another...", "BlankEditorRow", null, new { id = "addItem" })
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
jquery
$("#addItem").click(function () {
$.ajax({
url: this.href,
cache: false,
success: function (html) { $("#editorRows").append(html); }
});
return false;
});
$("a.deleteRow").live("click", function () {
$(this).parents("table.editorRow:first").remove();
return false;
});
Steven's helper
using System;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
namespace ef_tut.WebUI.Helpers
{
public static class HtmlPrefixScopeExtensions
{
private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";
public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
{
var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();
html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, html.Encode(itemIndex)));
return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
}
public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
{
return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
}
private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
{
string key = idsToReuseKey + collectionName;
var queue = (Queue<string>)httpContext.Items[key];
if (queue == null)
{
httpContext.Items[key] = queue = new Queue<string>();
var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
if (!string.IsNullOrEmpty(previouslyUsedIds))
foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
queue.Enqueue(previouslyUsedId);
}
return queue;
}
private class HtmlFieldPrefixScope : IDisposable
{
private readonly TemplateInfo templateInfo;
private readonly string previousHtmlFieldPrefix;
public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
{
this.templateInfo = templateInfo;
previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
}
public void Dispose()
{
templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
}
}
}
}
Use FormCollection, so instead of this:
[HttpPost]
public ActionResult Create(ClaimsViewModel viewModel)
{
}
You will have this:
[HttpPost]
public ActionResult Create(FormCollection formCollection)
{
}
You should be able to get the value for any new fields you have created using their name like so:
var newFieldValue = formCollection["newFieldName"];