How to update parent table from partial view using Ajax - c#

I have a table that lists the courses in the system with a Select button for each Course (in a row). When I click Select, the enrolled users of that course are displayed. The Course entity has navigation property public List<CourseRegistration> CourseRegistrations { get; set; }
I have this ViewModel for this purpose:
public class CourseIndexViewModel
{
public int SelectedCourseId { get; set; }
public List<Course> Courses { get; set; }
}
Just under the enrollments list (or registrations) I have a textbox (for keyword) and button to search users for enrollment. I use AJAX to execute an action of controller (UserController) (to which I pass the keyword) which searches users in the db, and passes the result set to a partial view, which returns a table of users with Enroll button in each row.
Everything works fine so far. Now, I need to implement the Enroll button inside the Partial View. However, I will need the id of the course, which is actually available in the main view (i.e., SelectedCourseId). Is there a way to access that value from the partial view? Do I have to (or should I) use hidden input for this purpose?
The biggest challenge is updating the enrollment list shown in the main View after enrolling a new user. I want to use Ajax to do that to prevent page refresh.
Is it feasible and recommended to use Ajax to get the enrollments again from the database and replace the existing enrollments table in the main view with the new table generated in the partial view?
UPDATE
Here is the main view:
#model EcholuMvc.Models.CourseIndexViewModel
<table class="table">
<tr>
<th></th>
<th>
CourseTitle
</th>
<th></th>
</tr>
#foreach (var item in Model.Courses)
{
<tr #(Model.SelectedCourseId == item.CourseId ? "style=background-color:whitesmoke;" : "style=" )>
<td>
#Html.ActionLink("Select", "Index", new { courseId = item.CourseId })
</td>
<td>
#Html.DisplayFor(modelItem => item.CourseTitle)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.CourseId }) |
#Html.ActionLink("Delete", "Delete", new { id = item.CourseId })
</td>
</tr>
if (Model.SelectedCourseId == item.CourseId)
{
<tr>
<td>
<h4>Enrolled users:</h4>
<table class="table">
<tr>
<th>First name</th>
<th>Last name</th>
<th></th>
</tr>
#if (item.CourseRegistrations.Count > 0)
{
var registrations = item.CourseRegistrations;
foreach (var reg in registrations)
{
<tr>
<td>
#reg.Member.FirstName
</td>
<td>
#reg.Member.LastName
</td>
<td>
#Html.ActionLink("Delete", "Delete", new { memberid = reg.MemberId, courseid = reg.CourseId })
</td>
</tr>
}
}
else
{
<tr>
<td colspan="4">No enrollment!</td>
</tr>
}
</table>
<div class="container-fluid">
<div class="row">
<div class="col-sm-9">
<input id="txt_SearchUser" placeholder="Enter a name.." class="form-control " type="text" />
</div>
<input id="btn_SubmitUserSearch" class="btn btn-default btn-sm col-sm-3" type="button" value="Search" />
</div>
<div class="row">
<div id="div_UserSearchResults" class="col-sm-12">
</div>
</div>
</div>
<script>
$("#btn_SubmitUserSearch").click(function () {
$.ajax({
url: 'Account/SearchUsers',
contentType: 'application/html; charset=utf-8',
data: { keyword: $('#txt_SearchUser').val() },
type: 'GET',
dataType: 'html'
})
.success(function (result) {
$('#div_UserSearchResults').html(result);
})
.error(function (xhr, status) {
alert(status);
})
});
</script>
</td>
</tr>
}
}
</table>
And, here is the partial view:
#model IEnumerable<EcholuMvc.Models.ApplicationUser>
<table class="table-striped" >
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
<select id="drp_Role">
<option value="Student" selected="selected">Student</option>
<option value="Instructor">Instructor</option>
</select>
</td>
<td>
<input id="btn_Enroll" data-userid="#item.Id" type="button" />
</td>
</tr>
}
</table>

If you simply want the selected courseId in client side (for your ajax submit of new enrollment or any other thing), you may add a hidden field to the main view and read it from that when needed.
#Html.HiddenFor(s=>s.SelectedCourseId)
Now whenever you need it for your ajax posts, just read the value of this and use
var selectedCourseId=$("#s.SelectedCourseId").val();
But If you want the courseId in your search functionality for some reason, you may pass the selected courseId to your ajax call as a parameter. Keep the course id as the html 5 data attribute to your search input field
<input id="txt_SearchUser" placeholder="Enter a name.."
data-course="#item.CourseId" class="form-control " type="text" />
Now when you make the ajax call, read this value and send it.
$("#btn_SubmitUserSearch").click(function () {
$.ajax({
url: 'Account/SearchUsers',
data: { keyword: $('#txt_SearchUser').val(),
courseId:$('#txt_SearchUser').data("course") },
type: 'GET',
dataType: 'html'
})
.success(function (result) {
$('#div_UserSearchResults').html(result);
})
.error(function (xhr, status) {
alert(status);
})
});
Make sure your SearchUsers endpoint accept this new param and pass that to the resulting partial view it will render.
public ActionResult SearchUsers(string keyword,int courseId)
{
// to do : Do something with the passed values
// to do : return something
}
Also i assume that, with your if condition you are rendering only one search form in your page because you cannot have duplicate id's. Also consider using Url.Action helper method to generate the proper url to the action method instead of hardcoding the url as explained in this post.

Related

ASP.NET MVC - Http 404 Error when calling HttpPost Method in Controller

I was looking around and trying to find a solution for my problem, but somehow I can not find the right answer.
I have an Index.cshtml view that looks like this:
#model IEnumerable<MyProject.Models.Conditions>
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm())
{
<style> ... </style>
<div class="container1">
<div class="box1">
<div class="box-cell1 box1">
<fieldset id="field1">
<legend>CustomerNumber</legend>
#Html.TextBox("s_custNum")
<input type="submit" value=">" style="height: 22px; width: 50px; padding:0px;" />
</fieldset>
</div>
</div>
</div>
<table class="tableMain" id="x">
<tr class="trMain">
<th class="thMain">
#Html.DisplayNameFor(model => model.ID)
</th>
<th class="thMain">
#Html.DisplayNameFor(model => model.NAME)
</th>
<th class="thMain">
#Html.ActionLink("Edit Lines", "EditAll", "EditAll", null)
</th>
</tr>
#foreach (var item in Model)
{
<tr class="trMain">
<td class="tdMain">
#Html.DisplayFor(modelItem => item.ID)
</td>
<td class="tdMain">
#Html.DisplayFor(modelItem => item.NAME)
</td>
<td class="tdMain">
<input type="checkbox" class="chkCheckBoxId" name="custId" value="#item.ID" />
</td>
</tr>
}
</table>
I'm currently listing all records as table on my site and each record has a checkbox. The header has an action link for redirecting the user to a new editing site. At the top of the site is a search Textbox.
The method in my controller looks like this:
[HttpPost]
public ActionResult EditAll(FormCollection collection)
{
var test = Request.Form.GetValues("custId");
string[] ids = collection["custId"].Split(new char[] { ',' });
return View();
}
Here I'm trying to get all the ids from the records that are checked. However, I get an error 404 that the page can not be found. When I remove the [HttpPost] attribute, the method gets called but returns no values in "test" or "ids".
I even tried to add values to my Html.BeginForm, but then the searchbar on the site does not work anymore.
Could someone help me retrieving the ids of the checked records? I'm new to ASP.NET MVC.
Many thanks!
The action link you created creates an anchor tag (https://www.w3schools.com/tags/tag_a.asp)
#Html.ActionLink("Edit Lines", "EditAll", "EditAll", null)
Anchor tags are a GETs, not POSTs.
What you are looking for since you already have a BeginForm() and an input button -- which would cause your submit button to post back to the "Index" action -- is maybe doing this in Javascript or putting multiple forms on the page.
Forms cannot be nested inside of each other, but you can have multiple on a page.
Alternatively, just change it to an [HttpGet] if that's what you are trying to do.
Edit:
Javascript example
How to send data in jquery.post to mvc controller which use ViewModel as parameter?
var myData = {
Parameter1: $("#someElementId").val(),
Parameter2: $("#anotherElementId").val(),
ListParameter: { /* Define IEnumerable collections as json array as well */}
// more params here
}
$.ajax({
url: 'someUrl',
type: 'POST',
dataType: "json",
contentType: 'application/json',
data: JSON.stringify(myData)
});
[HttpPost]
public JsonResult Create(CustomViewModel vm)
{
// You can access your ViewModel like a non-ajax call here.
var passedValue = vm.Parameter1;
}

Passing value to partial view via post

I want to pass a long string from my View to a partial View and render the partial View on Button click.
The string is too long to be passed as a part of the url, so it must be passed via post.
On the click of the Button Details, an ajax function is called, which should pass the required parameter to the partial view and render it.
Unfortunytely this doesn't work, it seems I have an error in my ajax-declaration. When I click the Details Button nothing happens, and the debugger jumps over the ajax declaration.
Here is a part of my View:
<form class="form-inline" asp-controller="Order" asp-action="Details">
<table class="ComplianceTable" id="ComplianceTable">
<thead>
<tr id='tableHeader'>
<th>Res.</th>
<th>Trend</th>
<th>Portfolio</th>
<th>Level</th>
<th>Code</th>
<th>Description</th>
<th>Rule</th>
<th>Test Type</th>
<th>Limit</th>
<th>Result</th>
<th>(Previous)</th>
<th>Severity</th>
<th>Details</th>
</tr>
</thead>
<tbody>
#foreach (var info in Model.ViolationInfo)
{
<tr>
<td>#info.RuleType</td>
<td></td>
<td>#Model.code</td>
<td></td>
<td></td>
<td></td>
<td>#info.RuleName</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>
<input type="hidden" id="value1" name="info.Diagnostic">
<button class="btn js-details" type="button" name="Details" data-id="#info.Diagnostic">
Details
</button>
</td>
</tr>
<tr class="info"><td colspan="11">#info.Diagnostic</td></tr>
}
</tbody>
</table>
<div id="getComplianceDetails"></div>
</form>
Here is my PartialView:
#model string
<div id="getComplianceDetails">
<p>Test</p>
<p>
#Model
</p>
</div>
Here is my javascript:
$(document).ready(function () {
$(document).on('click', '.js-details', function (event) {
$("#ComplianceTable tr").removeClass("selected");
$(this).closest('tr').addClass('selected');
var $element = $(event.currentTarget);
var id = $element.data('id');
$.ajax({
url: '#Url.Action("Details", "Order")',
data: {info: id},
type: "POST",
success: function (data) {
$('#getComplianceDetails').html(data);
}
});
});
});
Here is my OrderController:
public ActionResult Details(string info)
{
return PartialView("~/Views/Shared/complianceDetails.cshtml", info);
}
Assuming that your "info" has value, try an explicit url:
url: '/Order/Details',
and maybe you need to add [FromBody] to your action:
public ActionResult Details([FromBody] string info)
{
return PartialView("~/Views/Shared/complianceDetails.cshtml", info);
}

Error for bind data values in html.index with AngularJS

i am using AngularJS and I am not able to populate the data in index.view with the ng-repeat of angular.
I'll leave the code snippet for any help.
Remember, I have the status of the http 200 requests ok, just when I connect the data on the screen, I can not fill.
registerController.js
angular.module('Application').controller('registerController',
function($scope,
$http, registerService) {
$scope.registerUser = {};
$scope.GetAllRegisters = function () {
var registerServiceCall = registerService.GetRegisters();
registerServiceCall.then(function (results) {
$scope.registers = results.data;
}, function (error) {
$log.error('ERRO');
});
};
$scope.GetAllRegisters();
});
My service.js
angular.module('Application').factory('registerService', function ($http) {
return {
GetRegisters: function () {
return $http({
method: 'Get',
url: "http://localhost:51734/api/UserAPI"
})
},
};
});
And my index.html
<div class="row" style="">
<table class="table table-striped" style="">
<tbody>
<tr>
<th style="display:none">Id</th>
<th>Nome</th>
<th>Sobrenome</th>
<th>Ativo</th>
<th>Email</th>
<th>Editar</th>
<th>Remover</th>
</tr>
<tr ng-repeat="registerUser in registers" style="word-wrap: break-word;">
<td style="display:none">{{registerUser.UserId}}</td>
<td>{{registerUser.Name}}</td>
<td>{{registerUser.LastName}}</td>
<td><input type="checkbox" ng-model="registerUser.IsActive" disabled /></td>
<td>{{registerUser.Email}}</td>
<td>
<td>
</td>
</tr>
</tbody>
</table>
Any help or advice would be appreciated. Thanks
What is $scope.registers once the page loads?
As it stands right now your table will not render correctly because you cannot use ng-repeat on a tr because it will be inserted as a block-level element which will blow up your table. However, the data should still be inserted above your table. You will have to call ng-repeat on a custom directive to render the table properly.
Something like this:
<register-user-row ng-repeat="registerUser in registers"><register-user-row>
Then in the directive:
angular.module('Application').directive('regusterUserRow', function() {
return {
templateUrl: "directive path here",
restrict: "E",
scope: true
}
})
And the directive's html:
<tr style="word-wrap: break-word;">
<td style="display:none">{{registerUser.UserId}}</td>
<td>{{registerUser.Name}}</td>
<td>{{registerUser.LastName}}</td>
<td><input type="checkbox" ng-model="registerUser.IsActive" disabled /></td>
<td>{{registerUser.Email}}</td>
<td>
</td>
<td>
</td>
</tr>
Note: You were also missing a closing after your first link in the .

How to use ActionLink as submit button to Delete each row in a table?

I am developing a application where I need display details like ID, name and address of customers. And one should be able to edit or delete each customer details. For that I have added Edit and delete action links as buttons for reach row. In my controller I have added switch case to perform edit or delete actions.
My question is how to use action link as submit button. Or is there any better way to perform edit and delete records
View :
<table class="table">
<tr>
<th>
#Html.DisplayNameFor(model => model.id)
</th>
<th>
#Html.DisplayNameFor(model => model.name)
</th>
<th>
#Html.DisplayNameFor(model => model.address)
</th>
<th></th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.id)
</td>
<td>
#Html.DisplayFor(modelItem => item.name)
</td>
<td>
#Html.DisplayFor(modelItem => item.address)
</td>
<td>
#Html.ActionLink("Edit", "", new { id = item.id }) |
#Html.ActionLink("Delete", "", new { id = item.id })
</td>
</tr>
}
</table>
Controller :
[HttpPost]
public ActionResult MyTableView(int id, List<MyList> list)
{
switch (submitButton)
{
case "Delete":
break;
case "Edit" :
break;
}
return View ("MyTableView");
}
This is a method I often use since you asked if there is any different/better method. FYI This example is using FontAwesome Icons and Bootstrap 4 styling for the buttons.
I like to use Ajax and then update the frontend in one way or another depending on response.
Then if a user wants to edit a record I take them to a different page to edit that specific entry where I just use a standard viewmodel and razor form / #Html.EditorFor() stuffs
You'll notice the HTML has an #Item.Id which would be from an iteration of the viewmodel list data in a #foreach(var item in list) That's where the Id value in javascript is coming from.
Controller
public JsonResult Delete(int? id)
{
if (id == null)
{
return Json(-1);
}
var document = CommonService.GetDocument(id);
if (document == null)
{
return Json(-1);
}
if (0 == AdminService.DeleteDocument((int)id))
{
return Json(1);
}
return Json(-1);
}
Html
Have a row in the table like
<td class="text-nowrap"><a class="btn btn-primary text-white" href="#Url.Action("Edit", "Controller", new { id = item.Id })"><i class="fa fa-pencil-square-o"></i> Edit</a> <button value="#item.Id" class="btn btn-primary text-white delete"><i class="fa fa-trash-o"></i> Delete</button></td>
Javascript
$(".delete").unbind().click(function () {
var entryId = $(this).val();
if (confirm("Are you sure you want to delete?"))
{
var selected = $(this).parents("tr").addClass("selected");
$.ajax(
{
url: '#Url.Action("Delete", "Controller")',
type: "POST",
dataType: "json",
data: { id: entryId }
})
.done(function (data) {
if (data == 1) {
table.row(".selected").remove().draw(false); // Delete row from view
}
else {
Alert("Something went wrong...")
}
});
}
});
As #CodingYoshi suggested I did like this.
view :
<td>
#Html.ActionLink("Edit", "EditDetails", new { id = item.id }) |
#Html.ActionLink("Delete", "Delete", new { id = item.id })
</td>
When the use click on any link going to that action controller
public ActionResult Delete(int id)
{
//code to delete
}

MVC viewmodel is null on post (editing multiple rows)

I'm new to ASP.NET MVC and have been searching for a solution to this problem for hours. I'm trying to create a site where admins can go in and approve registration user requests and also assign them to a user group. I'm getting data from multiple tables so I created a viewmodel.
I finally have the GET Edit controller working to display the data, but can't figure out how the POST Edit should work. When I was debugging, I realized that the viewmodel I was trying to return had only null values.
I'm not sure if there are many things wrong with this code or just one. On postback, I need to update some values in the Access table. If you need more information from me, just let me know. Thanks!
ViewModel:
using System.Collections.Generic;
using AccessRequests.Models;
using System.ComponentModel.DataAnnotations;
namespace AccessRequests.ViewModels
{
public class UserAccessData
{
public IEnumerable<User> Users { get; set; }
public IEnumerable<Access> Accesses { get; set; }
public IEnumerable<UserGroup> UserGroups { get; set; }
}
}
Controller:
// GET: Accesses/Edit/5
public ActionResult Edit(string brand, string group)
{
var viewModel = new UserAccessData();
viewModel.Users = db.Users
.Include(i => i.Accesses)
.OrderBy(i => i.UserName);
viewModel.UserGroups = db.UserGroups
.Where(i => i.Groups.Contains(group));
if (brand != null)
{
viewModel.Accesses = db.Accesses
.Include(x => x.User)
.Where(x => x.Brand.ToUpper() == brand);
}
return View(viewModel);
}
// POST: Accesses/Edit/5
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit(Access access, UserAccessData editaccess)
{
//code here
}
View:
#model AccessRequests.ViewModels.UserAccessData
#{
ViewBag.Title = "Edit";
}
<h2>Edit Access</h2>
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div class="form-horizontal">
<table class="table">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Company</th>
<th>Role</th>
<th>Region</th>
<th>User Group</th>
<th>Approve</th>
<th>Deny Reason</th>
</tr>
</thead>
<tbody>
#foreach (var item in Model.Accesses)
{
#Html.HiddenFor(modelItem => item.UserName)
<tr>
<td>
#Html.DisplayFor(modelItem => item.User.FirstName)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.Email)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.Company)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.Role)
</td>
<td>
#Html.DisplayFor(modelItem => item.User.Region)
</td>
<td>
#Html.DropDownListFor(m => m.UserGroups, new SelectList(Model.UserGroups, "Groups", "GroupDescription"), "Please select a User Group")
</td>
<td>
#Html.DropDownListFor(modelItem => item.Approved, new SelectList(
new List<Object>{
new { value = 0 , text = "" },
new { value = "YES" , text = "YES" },
new { value = "NO" , text = "NO"}
},"value","text", 2))
</td>
<td>
#Html.EditorFor(modelItem => item.DenyReason, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(modelItem => item.DenyReason, "", new { #class = "text-danger" })
</td>
</tr>
}
</tbody>
</table>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script src="~/Scripts/jquery.validate.min.js"></script>
<script src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
In order to post to a collection property, your field names need to be in the form of something like: Accesses[0].Approved, Accesses[1].Approved, etc. In order to achieve that, you need to use a for loop rather than foreach. You'll also need to change your property's type from IEnumerable<Access> to List<Access>.
#for (var i = 0; i < Model.Accesses.Count(); i++)
{
...
#Html.DropDownListFor(m => Model.Accesses[i].Approved, ...)
}
Also, bear in mind that only those properties which have actual HTML fields that participate in the the post will have values in your post action. Everything else will either be null or whatever default value the property may have. If you save an entity with properties that have been nulled out because they weren't posted, you will overwrite those properties in your database. You need to take care to either make sure all the necessary data comes through in the post or that you repopulate said data from the the database before attempting to save anything.

Categories