I have a view Index which has a partial view GetAlertData referenced inside:
Index.cshtml
<table>
<thead>
<tr>
<th>Date</th>
<td>Message</td>
<td>Type</td>
<td>Actions</td>
</tr>
</thead>
<tbody id="tableBody">
#Html.Action("GetAlertData", new { selectedAlertType = Model.SelectedAlertType })
</tbody>
</table>
GetAlertData.cshtml
foreach (var alert in Model.UserAlerts)
{
<tr>
<td>
#alert.Date.ToString("d")
</td>
<td>
#alert.Message
</td>
<td>
#alert.AlertTypeName
</td>
<td>
#Ajax.ActionLink("Dismiss", "Dismiss", new { userAlertID = alert.UserAlertID }, new AjaxOptions() { HttpMethod = "Post" })
</td>
</tr>
}
Controller code
public PartialViewResult GetAlertData(string selectedAlertType = "All")
{
//create viewModel
return PartialView(viewModel);
}
[HttpPost]
public ActionResult Dismiss(int userAlertID)
{
alertModel.DismissAlert(userAlertID);
return RedirectToAction("Index"); //does nothing, because ajax (?)
}
The problem is this: I want the "parent" view to refresh the data from the "GetAlertData" Html.Action after clicking Dismiss. How can I get the "parent" view to rerun the GetAlertData after an alert is Dismissed? Returning a RedirectToAction does nothing because it's an ajax call.
I know I could set up a javascript method in the parent view, and then call that in the OnSuccess in the Dismiss ajax call, but that seems kinda messy and I was wondering if there's anything in the MVC framework or helper methods that would help me do this.
There's only two ways to update page content with something from the server: 1) Reload the entire page or 2) AJAX.
If you want to just update a portion of your page without causing a reload, then you must use AJAX to request some action that will return the information you desire and then JS to replace the portion of the page in the DOM. Since this is about essentially reloading a partial, you'll need an action that returns that partial. Also, since you're already using a child action, the same action can be used for this as well. Just make sure to remove [ChildActionOnly], if present, so the action is exposed to the routing framework.
If you move the ActionLink to a jQuery/Javascript click event then you can call the Dismiss from there returning a success/error flag. If the Dismiss was successfull then call the GetAlertData method with another Ajax call and use the returned HTML to replace the table body. You may need to make some other changes.
I solved this problem. I changed the redirect action from "Index" to "GetAlertData", and set the UpdateTargetId option to the same as it is in the parent view. After clicking the dismiss button, it replaces the content of the partial view with the new result.
The only thing that is smelly to me about this answer is that I have to reference the id of an element in the parent view ("tableBody"). I tried to wrap the whole child view in a div and replace that, but that resulted in the duplication and poor formatting. That might be partially because <table><div><tr> isn't exactly valid HTML.
New Child view code:
#{
var ajaxOpts = new AjaxOptions()
{
UpdateTargetId = "tableBody",
HttpMethod = "Post",
};
if (Model.UserAlerts != null)
{
foreach (var alert in Model.UserAlerts)
{
<tr>
<td>
#alert.Date.ToString("d")
</td>
<td>
#alert.Message
</td>
<td>
#alert.AlertTypeName
</td>
<td>
#Ajax.ActionLink("Dismiss", "Dismiss", new { userAlertID = alert.UserAlertID }, ajaxOpts)
</td>
</tr>
}
}
}
Related
I have a view where I display a list of users, setting the model as:
#model List<MyProject.Models.User>
In this view I want to be able to choose to perform an action on a specific user, i.e. post to the controller that I want to disable a user. How do I post the specific User object to the controller?
This is what I've got so far, but I can't see how to post the specific object from the collection:
#foreach (var c in Model)
{
<tr>
<td>#c.Username</td>
<td>#c.IsEnabled</td>
<td>
#using (Html.BeginForm("DisableUser", "UserManagement"))
{
<input type="submit" value="Disable" class="btn btn-primary"/>
}
</td>
</tr>
}
My controller has the signature:
public ActionResult DisableUser(User user)
Rather than posting back all propeties of User, you can just add a route value in the BeginForm() method to post back the ID or the User. Assuming that property is namedUserId`, then
#foreach (var c in Model)
{
<tr>
....
<td>
#using (Html.BeginForm("DisableUser", "UserManagement", new { id = c.UserId ))
{
<input type="submit" value="Disable" class="btn btn-primary"/>
}
</td>
</tr>
}
and the controller method would be
public ActionResult DisableUser(int id)
{
// Get the User based on id, update it and redirect
}
You could also consider using ajax to submit the value, which would allow the user to stay on the same page and continue to 'disable' other User objects without need to make a redirect, in which case the code might be
#foreach (var c in Model)
{
<tr>
....
<td>
<button type="button" class="disable" data-id="#c.UserId">Disable</button>
</td>
</tr>
}
var url = '#Url.Action("DisableUser", "UserManagement")';
$('.disable').click(function() {
var row = $(this).closest('tr');
$.post(url, { id: $(this).data('id') }, function(result) {
if(result) {
// for example, remove the row from the table
row.remove();
} else {
// Oops
}
}).fail(function (result) {
// Oops
});
});
and the controller method would be
public JsonResult DisableUser(int id)
{
// Get the User based on id and update it
return Json(true);
// or if the update failed - return Json(null);
}
The simple way to disable a user is to use a Html.ActionLink instead of the form - you should be able to see plenty of examples of this in the template code. The action link could redirect to a confirmation page or you could just disable the user and redirect to a message page saying "the user has been disabled".
I better way is to use AJAX. You can do this with jQuery or you could use the MVC Ajax form or Ajax Action Link. I would recommend that you google MVC Ajax Action Link examples.
You may also want to style the link by setting it's class to the Bootstrap 'btn' class.
There is a way to post a single user
#foreach (var c in Model)
{
<tr>
<td>#c.Username</td>
<td>#c.IsEnabled</td>
<td>
#using (Html.BeginForm("DisableUser", "UserManagement"))
{
<input type="text" name="Username" value="#c.Username">
<input type="text" name="IsEnabled" value="#c.IsEnabled">
<input type="hidden" name="id" value="#c.id">
<input type="submit" value="Disable" class="btn btn-primary"/>
}
</td>
</tr>
}
The ideas is same,due to lack of time, please proceed according to your requirements, use the Class Property name as input name, the action will get the value
First of all you must not declare Html.BeginForm inside of any loop to post the object to controller.
#using (Html.BeginForm("DisableUser", "UserManagement"))
{
#foreach (var c in Model)
{
<tr>
<td>#c.Username</td>
<td>#c.IsEnabled</td>
<td>
<input data-id="#c.Id" type="submit" value="Disable" class="clsBtnPost btn btn-primary"/>
</td>
</tr>
}
}
This is for your reference
$(document).ready(function(){
$(".clsBtnPost").click(function(){
var userId = $(this).data("id");
$.ajax({
contentType: 'application/json; charset=utf-8',
dataType: 'json',
type: 'POST',
url: 'ControllName/MethodName', //Your Url
data: { 'userId': userId },
success: function () {
alert("successfully called.");
},
failure: function (response) {
alert("Error");
}
});
});
});
You can go with the Ajax Call to pass a single object to controller.
I have a jQuery view that show a list of Fix object, this one:
<ul data-role="listview" data-inset="true" data-theme="b" data-split-icon="delete">
#foreach (DataModel.Vulnerability.Fix item in Model.VulnerabilityFixes)
{
<li><a href="#Url.Action("Details", "Product", new { Title = item.Title })">
<h2>#item.Title</h2>
<table style="width: 100%">
<tr>
<th>Id</th>
<th>FixName</th>
<th>Vendor</th>
<th>Version</th>
</tr>
<tr>
<td>#MyHelper.decodeNull(item.Id)</td>
<td>#MyHelper.decodeNull(item.FixName)</td>
<td>#MyHelper.decodeNull(item.Vendor)</td>
<td>#MyHelper.decodeNull(item.Version)</td>
</tr>
</table>
</a>
Delete
</li>
}
</ul>
As you can see, this list contains a button (a link) to delete the related fix, this one:
Delete
When this link is clicked is called the DeleteFix() method of the EditingController class that take 2 paramethers:
vulnId that is a long value.
The Fix object related to the clicked link line.
So this is my DeleteFix() method,
public ActionResult DeleteFix(long vulnId, DataModel.Vulnerability.Fix currentFix)
{
DataModel.Vulnerability.Fix model = new DataModel.Vulnerability.Fix();
manager.openConnection();
try
{
model = currentFix;
}
finally
{
manager.closeConnection();
}
return View(model);
}
The problem is that the when enter into the method the vulnId parameter is correctly set but the currentFix parameter is null.
What am I missing? What can I do to correctly pass the Fix object related to the line on which I pressed the delete button?
You can't pass an "object" in a URL. You can only pass text. (Although you could serialize the object). You probably want to pass the Id of the item. Thus the link should look like:
Delete
You action should be:
public ActionResult DeleteFix(long vulnId, int currentFix)
Then you can use the Id to delete the item.
I have this fiddle here. It demonstrates a hover over and a box appearing with info in it.
What I'm trying to achieve is, when the "View Details" is hovered over, it triggers the MVC action Details(guid Id) and the result of that action is rendered in the box.
I'm not entirely sure how to do this. I would assume that a AJAX form is submitted on hover, so this will need to be done by JS (I really don't know how to do AJAX with JS). Then the div would be displayed with a newly rendered #Html.Action("Detail", "Stuff", new { Id = #item.Model.Id })
Am I close?
The View would be something like this
<table>
<thead>
<tr>
<td>Name</td>
<td>E-mail</td>
</tr>
</thead>
<tbody>
#foreach (var item in Model.ItemList)
{
<tr>
<td>#Html.DisplayFor(model => item.Name)</td>
<td>#Html.DisplayFor(model => item.Email)</td>
<td>#using(Ajax.BeginForm(ajaxOpts))
{
<span>Hover for Details</span>
#Html.HiddenFor(model => item.Id)
}
</td>
</tr>
}
</tbody>
</table>
The action would purely be:
[ChildActionOnly]
public ActionResult Detail(Guid id)
{
var item = _repository.Stuff.FirstOrDefualt(x => x.Id.Equals(id));
return View(item);
}
So the specification is:
When "Hover for Details" has been hovered to show a box where the cursor is displaying the recently got details from the database
I've wrote this all off the top of my head, so don't scrutinise it for accuracy, its purely to demonstrate an idea, i'm not looking for errors in my code. I'm looking for ideas with valid working examples. Thanks.
1) Submit Ajax on Hover.
2) Follow the example here Render a view as a string to render your view as a HTML string within the server.
3) Use $("#showme").html(ReturnedHTMLData); to place the returned html into the DOM object.
i.e. server side
public JsonResult GetDetail(Guid id)
{
return Json(ViewToStringMethod(ViewName, id), "rest of Json allowget stuff");
}
i.e. Client side
$("#DomElement").on("hover", function(){
$.getJSON("GetDetail", {id: $('this').attr("id"), function(returnedData){
$("#showme").html(ReturnedHTMLData);
}
});
I've currently got a View like this:
#using (Html.BeginForm("Compare", "Carousel", FormMethod.Post))
{
#foreach (var product in Model.Products)
{
<tr>
<td>#product.Provider.Name</td>
<td>£#Math.Round(product.MonthlyPremium, 2, MidpointRounding.AwayFromZero)</td>
<td>#Html.ActionLink("More Details", "MoreDetails", new { id = product.ProductId })</td>
<td><button name="compare" value="#product.ProductId">Compare</button</td>
</tr>
}
}
Controller:
[HttpPost]
public ActionResult Compare(ProductsWithDetailModel model) // This is a guess, Model may not persist?
{
// Code here
return View("Index", model);
}
What I actually want is, when a button is clicked, the Controller is going to be called and the product that has been selected (the button clicked) will be added to a list of items.
How do I actually find out which button has been clicked in the Controller? How come I cant find any tutorials out there on the web that have done this before, surely its basic functionality? Or am I approaching it wrong?
ASP.NET MVC takes all inputs and send it by a verb to an action. The bind of these controls is made by the name attribute on your View and the value is made by depending of what control we are talking about (text, checkbox, radio, select, button etc...). You could have a a property on your ProductsWithDetailModel called compare and it would recevie the value of the button you've clicked.
How about having 1 form per item?
E.g.
#foreach (var product in Model.Products)
{
<tr>
<td>#product.Provider.Name</td>
<td>£#Math.Round(product.MonthlyPremium, 2, MidpointRounding.AwayFromZero)</td>
<td>#Html.ActionLink("More Details", "MoreDetails", new { id = product.ProductId })</td>
<td>
#using (Html.BeginForm("Compare", "Carousel", FormMethod.Post))
{
<input type="hidden" name="productId" value="#product.ProductId" />
<button name="compare">Compare</button>
}
</td>
</tr>
}
use onclick. set js hidden value in loop. in controller check.
I am new to ASP.NET MVC, so please help me to solve this, at a first glance, simple issue.
I have a table of students on my View and a Load More link below it. By clicking on this link I need to load more students records with AJAX.
So this is my View (omitted unnecessary data)
<table id="studentTable">
<thead>
...
</thead>
<tbody>
#Html.Partial("_StudentDetailsList", Model)
</tbody>
</table>
#Ajax.ActionLink("Load More", "LoadMoreStudents", new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "studentTable"
})
_StudentDetailsList partial view to display students
#model IEnumerable<MvcApplication1.Models.Student>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
</tr>
}
action
public ActionResult LoadMoreStudents(int skip = 0)
{
return PartialView("_StudentDetailsList", studentRepository.Get(orderBy: s => s.OrderBy(st => st.FirstName).OrderBy(st => st.LastName)).Skip(skip).Take(10));
}
So as you can see, I want to load next 10 students every time, but I have to know how much are already loaded. So the question is: from where should I get this skip parameter which I want to pass to the Action from the View. How can I count on the View how many partial views are already loaded and how many students each partial has? Or is it a bad practice to pass this parameter to action, maybe I should handle it somewhere else (e.g. some service)? Please advice the right way to do this. Thanks.
P.S. Please feel free to edit the caption as I can't come up with appropriate one.
The easiest method would be to create a ViewModel that contains these variables for you.
ViewModel
public class YourViewModel(){
public int skip {get;set;}
public IEnumerable<StudentDetails> StudentDetailList {get;set;}
}
The Controller/Action Method
public ActionResult LoadMoreStudents(int skip = 0)
{
return PartialView("_StudentDetailsList", new YourViewModel{
StudentDetailList = studentRepository.Get(orderBy: s => s.OrderBy(st => st.FirstName).OrderBy(st => st.LastName)).Skip(skip).Take(10),
skip = skip + 10});
}
The View
#Html.Partial("_StudentDetailsList", Model.StudentDetailList)
The Partial View
#model MvcApplication1.Models.YourViewModel
<table id="studentTable">
<thead>
...
</thead>
<tbody>
#foreach (var item in Model.StudentDetailList)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.LastName)
</td>
<td>
#Html.DisplayFor(modelItem => item.FirstName)
</td>
</tr>
}
</tbody>
</table>
#Ajax.ActionLink("Load More", "LoadMoreStudents", new {skip = Model.skip}, new AjaxOptions
{
HttpMethod = "POST",
InsertionMode = InsertionMode.InsertAfter,
UpdateTargetId = "studentTable"
})
Basically, you have to be able to dynamically update the Ajax.ActionLink with values coming back from the controller. Here, I have moved it into the PartialView (arguabley you could move the entire table including the fetch more link into the PartialView and have it be even cleaner).
You are on the way. You should replace the Ajax.ActionLink for a link with $.get, and retrieve the skip by counting the numbers of rows at the tbody that you already have:
First replace the ActionLink for:
Load More Students here!
Second, create this function to do the get:
$('mylink').click(function (e) {
e.preventDefault();
//Gets the number of rows that you alredy have loaded
var rowCount = $('studentTable').find('tbody > tr').length;
//The URL for the get, with the number of rows
var url = "http://www.myurl.com/controller/action?skip=" + rowCount;
$.get(url, function (data) {
//Append the content from the partial view to the tbody
$('studentTable').find('tbody').append(data);
});
});
I think that this is a good way as well!
Hopes this help you!