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!
Related
I've developed an asp.net MVC web app where I have a table that shows some items in a model.
I can filter it now with a dropdown list using ajax
The model that i pass to the table is correct (if i go to the model before the foreach there are 3 rows instead of 10 thanks to the filter)
The problem is that the table doesn't change, it always shows all the rows as the initial request.
It look like it works but the table won't update...
There's my jquery ajax call:
$("#Dropdown1Id").on('change', function () {
//console.log("onchange");
//console.log($("#Dropdown1Id").val());
var drpdown1 = $("#Dropdown1Id").val();
var submit = $("#submitButton");
$.ajax({ // crea una chiamata AJAX
data: { data: drpdown1 }, // prendi i dati del form in questo caso del primo dropdown
type: "GET", // GET o POST
url: "/Select/Filter", // li passa al controller
success: function () { // se va con successo esegue il codice seguente
submit.click();
$("#frmId").submit();
},
error: function (error) {
console.log("error")
}
});
});
There's my controller action:
public ActionResult Filter(string data)
{
List<Card> cards = new List<Card>();
ViewBag.stato = new SelectList(myApi.GetState(), "Name", "Name");
if (data != null && data != "")
{
foreach (var card in model)
{
if (card.IdList == data || data == "")
cards.Add(card);
}
return View(cards);
}
return View(model);
}
There's my view with the daple and the dropdown:
#using (Html.BeginForm(new { id = "frmId"}))
{
#Html.AntiForgeryToken()
<table id="tb2">
<tr>
<th>
<h4> LIST : #Html.DropDownList("stato", null, new { #id = "Dropdown1Id" })</h4>
</th>
#*<th>
<h4>ARCHVIED : #Html.DropDownList("closed", null, new { #id = "Dropdown2Id" })</h4>
</th>*#
<th>
<input type="submit" value="Filter" class="btn btn-info" id="submitButton" />
</th>
</tr>
</table>
<br />
<div id="risultato"></div>
<table class="table" id="tb1">
<tr>
<th style="text-align:center">
TRELLO'S CARDS LIST
</th>
<th>LIST</th>
<th>ARCHVIED</th>
<th>Expiration date</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.DisplayFor(modelItem => item.IdList)
</td>
#if (item.Closed == "True")
{
<td>YES</td>
}
else
{
<td>NO</td>
}
#if (item.Due != null)
{
<td>
#Html.DisplayFor(modelItem => item.Due)
</td>
}
else
{
<td>
Not Present
</td>
}
</tr>
idList.Add(item.Id);
}
</table>
Let me get you through the execution stack and you'll understand why:
Your MVC view is loaded. When the view is returned to the frontend it is already in Html format. Check Server side rendering here
Basically it means that #foreach (var item in Model) will only execute on the server side and will not re-run when you hit an ajax call. This will only happen on a full post.
While in your page you fire up change dropdown event and the following happens:
An ajax call hit your controller
Data are being returned to the success function
Your success: function () is being executed.
A new form post occurs. See that you didn't do anything with the return data that was returned in the success: function(). You just posted back to the controller
After the post, the full view has returned ignoring any changes in the dropdown and in the data returned.
There are 2 solutions for your problem:
Do a full post and return a new view with the proper data
Write some more javascript to change the DOM inside your sucess function
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>
}
}
}
I am learning MVC by converting an old Web Form to MVC 5. The page is pretty simple, just a few text boxes, a submit button and a grid. I want similar functionality where there is no grid on page load, and after the submit takes place, the grid appears. The issue I am having is all the examples I have seen contain the data coming at page load.
Here is my View
<h2>Search Craigslist</h2>
#using (Html.BeginForm())
{
<p>
Enter Search Criteria:<br />
#Html.TextBox("Criteria", null, new { #style = "width: 450" })
#Html.DropDownList("DropDownValues")
Days:
#Html.TextBox("Days")
<br />
<input type="submit" value="Submit" />
</p>
}
#Html.Partial("~/Views/Search/_Search.cshtml")
and the partial
#model IEnumerable<CraigsListSearch.MVC.Models.SearchModel>
#{
Layout = null;
}
<table cellspacing="0" border="1" style="border-collapse:collapse;">
<tr>
<th>
#Html.DisplayNameFor(model => model.Location)
</th>
<th>
Link
</th>
<th>
#Html.DisplayNameFor(model => model.DateSubmitted)
</th>
</tr>
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Location)
</td>
<td>
#Html.Hyperlink(Html.DisplayFor(modelItem => item.URL).ToString(), Html.DisplayFor(modelItem => item.Title).ToString())
</td>
<td>
#Html.DisplayFor(modelItem => item.DateSubmitted)
</td>
</tr>
}
and finally the controller
public class SearchController : Controller
{
[HttpGet]
//public ViewResult Index(string DropDownValues, string Criteria, string Days)
public ViewResult Index()
{
Populate();
Session["list"] = SearchModel.GetQuickList();
return View(new List<SearchModel>());
}
[HttpPost]
public ViewResult Index(string DropDownValues, string Criteria, string Days)
{
Populate();
Criteria = Server.UrlPathEncode(Criteria).Replace(",", "%2c");
List<Location> list = (List<Location>)(Session["list"]);
List<SearchModel> results = SearchModel.Search(Criteria, Days, DropDownValues, list.Take(5).ToList());
return View(results);
}
private void Populate()
{
List<DropDownModel> ddlList = DropDownModel.GetDropdowns();
ViewBag.DropDownValues = (from a in ddlList
select new SelectListItem
{
Text = a.Text,
Value = a.Value
}).ToList();
}
}
What I am looking for is to have the partial only be "called" on POST. Is this something that can easily be done? Let me know if other info is needed.
Thanks!
There are a couple of ways you could approach solving this problem.
One option would be to use AJAX. When the user clicks the button make an AJAX call to the controller and have the controller render the partial and return the HTML which you then inject into the page DOM.
Another option is to add a "DisplayGrid" property to your model. When you first render the View set it to false and then set it to true after the post.
One point I want to make is that rendering a View from the Post action isn't a great idea. If the user hits Refresh in their browser it's going to post again, giving the user that annoyingly ugly dialog that tells them they're going to send data to the server. In general you should use a Post-Redirect-Get pattern, the user posts, you redirect, and then you use a get to re-render in the browser. If you use the AJAX approach this all becomes moot because you never post the whole form back to a controller action. If you continue to use a full post the HttpPost version of Index should return a RedirectToAction result sending the user back to the Get version of Index. You can communicate between the two actions by using TempData, for example:
[HttpGet]
public ActionResult Index()
{
if( TempData.ContainsKey( "DisplayGrid" )
{
// Use the other values from TempData to populate the model with the grid data
myModel.DisplayGrid = (bool)TempData["DisplayGrid"];
}
}
[HttpPost]
public ActionResult Index( string dropDownValues, string criteria, string days )
{
TempData["DisplayGrid"] = true;
TempData["DropDownValues"] = dropDownValues;
TempData["Criteria"] = criteria;
TempData["Days"] = days;
return RedirectToAction( "Index" );
}
You should pass whatever information you need to render view via model (or ViewBag).
Since you already have sample of how to use Model here is how you can use ViewBag
#if(ViewBag.RenderSearch)
{
#Html.Partial("~/Views/Search/_Search.cshtml")
}
And set RenderSearch in particular action:
public ViewResult Index(string DropDownValues...
{
....
ViewBag.RenderSearch = true;
With model just add property to your model (you'd need to make it custom class instead of just List) and check that flag instead.
Try This, the Ajax only replace the GridDiv only
<h2>Search Craigslist</h2>
using (Ajax.BeginForm("BindGridAction", "Contoller",new AjaxOptions { UpdateTargetId = "GridDiv" }))
{
<p>
Enter Search Criteria:<br />
#Html.TextBox("Criteria", null, new { #style = "width: 450" })
#Html.DropDownList("DropDownValues")
Days:
#Html.TextBox("Days")
<br />
<input type="submit" value="Submit" />
</p>
}
<div id="GridDiv">
#Html.Partial("~/Views/Search/_Search.cshtml")
</div>
Bind your partial view in controller
public PartialViewResult BindGridAction()
{
// your model coding
return PartialView("~/Views/Search/_Search.cshtml", model);
}
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);
}
});
In my web page, I have a series of tables that basically just contain rows of information. Each of these is given an id in a for loop and I'm trying to reference them from outside that. I added classes to both the table and a 'Save Changes' button.
Essentially, my goal is for the user to be able to drag and drop rows around, thereby changing the order. Then they can click the 'Save Changes' button and this will post back to the server with the relevant information.
I am having trouble matching up the button to the relevant table and thereby submitting the id's of each row back to the server in an array. I have written the code to be able to be able to get the ids from each of the tables and their current order, but I don't know how to assign this to an array from within the button click jQuery.
Here is the View:
#foreach (var collection in Model.Collections)
{
<h2>#collection.Season</h2>
#Html.ActionLink("Delete Collection", "DeleteCollection", new { controller = "Edit", brand = collection.Brand.Name, season = collection.Season })
#Html.ActionLink("Edit Collection", "EditCollection", new { controller = "Edit", brand = collection.Brand.Name, season = collection.Season })
#Html.ActionLink("Add Image", "CreateImages", new { controller = "Edit", season = collection.Season })
<p>
To change the ordering of images, drag and drop to your desired position and then click the Save Changes button on the appropriate collection.
</p>
<table class="table-collection" id="table-#collection.Id">
<tr class="nodrop nodrag">
<th>
Id
</th>
<th>
Description
</th>
<th>
Image
</th>
<th>
Options
</th>
</tr>
#foreach (var image in collection.Images)
{
<tr id="#collection.Id-#image.Id">
<td class="dragHandle showDragHandle">
#image.Id
</td>
<td>
#image.Description
</td>
<td>
<img src="#Url.Content("~/" + image.Location)" alt="#image.Description" />
</td>
<td>
<ul>
<li>
#Html.ActionLink("Edit", "EditImage", new { controller = "Edit", brand = image.Collection.Brand.Name,
season = image.Collection.Season, imageId = #image.Id } )
</li>
<li>
#Html.ActionLink("Delete", "DeleteImage", new
{
controller = "Edit",
brand = image.Collection.Brand.Name,
season = image.Collection.Season,
imageId = #image.Id
})
</li>
</ul>
</td>
</tr>
}
</table>
<p>
<input type="submit" value="Save Changes" class="save-order" id="saveTable-#collection.Id"/>
</p>
}
Here is the jQuery so far:
$(document).ready(function () {
$(".table-collection").tableDnD();
$(".save-order").click(function (e) {
e.preventDefault();
$.ajax({ url: window.location.href,
type: 'POST',
data: { ids: $("--ASSIGN ARRAY HERE--"
});
The jQuery to iterate through each row is essentially this:
function(table, row) {
var rows = table.tBodies[0].rows;
var debugStr = "Row dropped was "+row.id+". New order: ";
for (var i=0; i<rows.length; i++) {
debugStr += rows[i].id+" ";
}
I see you are using input type submit which is exclusively used to postback forms. What you need to do is wrap every table up in a form with something like this:
#using(Html.BeginForm("Action", "Controller", new{ collectionId = collection.Id }))
{
<input type="submit" value="Save Changes" class="save-order" />
}
Note that this will cause a 'post-back' of the form to Action, Controller. Specify the collection id inside the route values to identify the specific collection.
Do note, you need to add input type hidden with the id's value otherwise the ids' won't get serialised - all you have to specify is the name attribute
<td class="dragHandle showDragHandle">
<input type="hidden" name="ids" value="#(image.Id)" />
#image.Id
</td>
Then you can intercept the call then do it via ajax with:
$(".save-order").click(function(e) {
e.preventDefault();
var form = $(this).closest('form');
if(form.validate()) {
$.post(form.attr('action'), form.serialize(), function() {
alert('The new image order has been saved.');
});
}
return false;
});
The accepting controller action method will probably have this signature
public ActionResult Action(int collectionId, int[] ids)
{
//Do stuff here
return Request.IsAjaxRequest() ? null : View();
}
Now it should support graceful degradation if javascript is disabled (does a normal form submit, otherwise does it via ajax)
Hope this helps :)
You can grab all of the IDs with something like this:
var IDs = [];
$("#mydiv").find("span").each(function(){ IDs.push(this.id); });
In your scenerio, do something like this:
$(document).ready(function ()
{
$(".table-collection").tableDnD();
$(".save-order").click(function (e)
{
var IDs = [];
$("#yourtable").find("draggable-tr-class").each(function(){ IDs.push(this.id); });
e.preventDefault();
$.ajax(
{
url: window.location.href,
type: 'POST',
data: { ids: IDs }
);
}
})
i have been create demo in jsfiddle using json
http://jsfiddle.net/viyancs/4ffb3/11/
if you use like that demo in your server must be get parameter `JSONFile' after that parse this json for what do you want.actually the demo not same with your case but i think you can use this by your logic.