Index ViewResult not re-rendering after callback from ajax call - asp.net

I'm trying to callback into my ViewResult Index() controller action from an ajax call to update the page contents based on a dropdown select but my view is not re-updating (re-rendering).
I have set breakpoints and the index() action in the controller is being executed as invoked from the ajax 'get' and the model is being passed off to the view (breakpoints are being hit in the view as well).
View:
#* Contains code to build a webgrid and display data based on the model passed in... *#
#* Contains a couple of dropdowns for filtering *#
#*
Catch the select event from a dropdown and call back into the view to re-update page contents
for filter requests.
*#
<script type="text/javascript">
$("select").multiselect({
click: function (event, ui) {
$.ajax(
{ type: "GET",
url: '#Url.Action("Index","Data")',
data: { FilterRequest: (ui.checked ? 'checked' : 'unchecked') },
success: function () {
alert('hello again');
}
})
}
});
</script>
Controller:
// GET: /Data/
public ViewResult Index(string FilterRequest)
{
IList<DataModel> dataResult;
if (FilterRequest == null)
{ // Not a filter request so just update grid with full contents
dataResult = db.DataObjs.OrderByDescending(x => x.id).ToList();
}
else
{ // Filter request so update grid with filtered data
dataResult = db.DataObjs.Where(/*Build some filtered stuff here*/).OrderByDescending(x => x.id).ToList();
}
// Build some sub totals based on the resultset from above result set to display
// Other business logic number mashing here to display in other grids on the same view
return View(dataResult);
}

You're not doing anything with the response of the $.ajax call.
Something like this:
$.ajax(
{
type: 'GET',
url: '#Url.Action("Index","Data")',
data: { FilterRequest: (ui.checked ? 'checked' : 'unchecked') },
dataType: 'html',
success: function (html) {
$('#somecontainer').html(html);
}
});
Also, you can't return a full view (e.g a HTML page) from your action method - you need to either return a PartialView, or a JsonResult which you can iterate over and manualy bind the contents.
For a partial view, you need something like this:
return PartialView(dataResult);
It all depends on what your trying to re-render. If the HTML you require to re-render is complex, then use a partial view. If it's simply a bunch of data that is to be shoved into an input element (e.g a dropdown list), you should save on the HTTP payload over the wire and use JsonResult.

Related

Editing a viewmodel's member via button without submit

I'm using Asp Net Core 3.1 and am working on developing admin controls to approve and delete submitted images that are awaiting approval. The functionality that I am developing and am stuck on is as follows: I have created a grid of images waiting approval (using a loop in razor) and would like to click a button to "approve" that image via the logic I have written in my controller. How would I pass that data to the controller without refreshing the page?
View Model
public class ImageManagerViewModel
{
public List<ListingImages> Images;
public List<Tuple<long, string>> ListingNames;
}
Controller
public class AdminController : Controller
{
public ActionResult ApproveImage(int listingID, long imageID, bool isFeatured)
{
....
}
}
Client-side
#foreach (ListingImages row in Model.Images)
{
....
<div class="d-flex card-footer">
<a a class="btn btn-success btn-space"
href="#Url.Action("ApproveImage", "Admin", new { listingID = row.ListingId, imageID = row.ImageId, isFeatured = false})" role="button">Approve</a>
}
As VDWWD described, you wanna use ajax to achieve this behavior.
I made a quick sample for your code (I didn't have the ability to test it atm though).
Your loop (you can also use hidden input fields to track the ids of every single item):
#foreach (ListingImages row in Model.Images)
{
...
<span class="imageId">#(row.ImageId)</span>
<span class="listingId">#(row.ListingId)</span>
<input type="button" class="btn btn-success approveBtn">Approve</button>
}
JQuery code in the script section:
<script>
$(document).on("click",
".approveBtn",
function () {
var imageId = $(this).closest(".imageId").text();
var listingId = $(this).closest(".listingId").text();
$.ajax({
url: "/Admin/ApproveImage",
type: "POST",
data: {
__RequestVerificationToken: $('input[name=__RequestVerificationToken]').val(),
listingID : listingId,
imageID: imageId,
isFeatured: false
},
timeout: 5000,
success: function(results) {
// result action
},
contentType: "application/x-www-form-urlencoded; charset=utf-8"
})
.fail(function (xhr, textStatus, errorThrown) {
// error handling
});
});
</script>
Hints:
If you use one, include the antiforgery token in the request as shown in the sample.
You can also send the payload as JSON. You then need to edit the content type and use JSON.stringify in the data section to convert the payload.

Razor #Html.ActionLink not getting passed control value correctly

I am new to Razor. I am making good progress on this project but have hit a major road block with something that would seem to be easy. I have read a lot of posts about how to pass the value of a control as a parameter to a controller in order to redirect to a new view. The problem is that I either get the value passed to the controller but can't redirect OR I redirect and the parameter is not passed.
This is my latest attempt. I was hoping to pass the return of GetSelectedEmail to the controller (the value of "selectedEmail"). I can see that the Javascript is getting the correct value and the controller is being called, but the value is always NULL.
#Html.ActionLink("Get Scoring Report...", "History", "Student", null, new { onclick = "return GetSelectedEmail();" });
<select id="selectedEmail" name="align">
#foreach( var s in Model.Students )
{
<option id=#s.Email>#s.Email</option>
}
</select>
function GetSelectedEmail() {
var str = "new {email=" + $("#selectedEmail").val() + "}";
return str;
}
The controller...
public ActionResult History(string email, string sort)
{
string localEmail="";
if ( email == null || email == "" )
localEmail = AccountProfile.CurrentUser.UserName;
...
I have also tried to call the controller with Ajax like below. The controller does get the "selectedEmail" parameter but the page never redirects. I just does nothing. I tried both having the action link with the link parameters or not (show below).
#Html.ActionLink("Get Scoring Report...", "", "", null, new { onclick = "return GetSelectedEmail();" });
<select id="selectedEmail" name="align">
#foreach( var s in Model.Students )
{
<option id=#s.Email>#s.Email</option>
}
</select>
function GetSelectedEmail() {
$.ajax({
url: '/Student/History',
data: { email: $("#selectedEmail").val(), sort: "CaseID" },
type: 'POST',
dataType: 'json',
});
return true;
}
Any ideas?
Your first approach is not actually doing the redirection. ( also it calls a different method, which i am assuming a copy paste mistake)
Your current code is not passing the values because it is a link and when it is clicked, it is supposed to navigate to that url, which is exactly what it is doing.
I just changed the code to use unobtrusive javascript. Replaced the onclick with an id for the link
#Html.ActionLink("Get Scoring Report", "History", "Student", null, new { id="score" });
and when the click happens on this link, read the value of the select element and navigate to the second action method by setting the location.href property value
$(function () {
$("#score").click(function(e) {
e.preventDefault(); // Stop the normal redirection
var url = $(this).attr("href"); //Get the url to action method
url += "?email=" + $("#selectedEmail").val(); //append querystrings
window.location.href = url; // redirect to that url
});
});
For what I needed the solution was simple.
Show Student Scores
<select id="selectedEmail" name="align">
#foreach( var s in Model.Students )
{
<option id="#s.Email">#s.LastName,#s.FirstName</option>
}
</select>
And the Javascript magic...
function GetScoreHistory() {
var emailVal = $('#selectedEmail').find('option:selected').attr('id');
var url = '#Url.Action("History", "Student")';
url += "?email=" + escape(emailVal);
window.location.href = url;
}
The controller was called exactly how I needed it to be called.

Pop-up dialog using jQuery issue

Hi I have a problem with my page. I have one view page and 2 forms in the same page.
The problem is that I have a main form and another form which is a shown by JQuery. My Problem is when I open the dialog box, submit its form and return the view, the dialog box diappears. I don't know how to return a result which will still show the opened the dialog box.
I need your help on this please!
Below are the codes I used in my application.
.CSTHML Forms
#using (Html.BeginForm("Login2", "Account", FormMethod.Post, new { #id = "loginForm" }))
{
<a id="submitlink" href="#" class="button button-large">Sign In</a>
}
// This is the pop-up dialog box
#using (Html.BeginForm("TroubleSubmit", "Account", FormMethod.Post, new { #id = "troubleSubmitForm" }))
{
<a id="troubleSubmitLink" href="#" class="button">OK</a>
}
JQUERY
$('a#submitlink').click(function () {
$('#loginForm').submit();
});
$('a#troubleSubmitlink').click(function () {
$('#troubleSubmitForm').submit();
});
Below is the code of my controller action to handle the dialog form submit:
public ActionResult SignInTrouble(some parameter...)
{
// some operation and validation here
return View(); // ??? What view will I return? When I return "Login" it will reload the page and close the dialog box.
}
Again, how do I return the View that will still display the dialog box
You're submitting your form in a traditional (non-AJAX) manner so the page will reload. So you'll need to post in an AJAX way.
$("#submitlink").on("click", function () {
var loginForm = $("#loginForm");
$.ajax({
url: loginForm.attr("action"),
type: "post",
data: loginForm.serialize(),
})
.done(function(result) {
// do something with the returned response
});
});
The successful response is handled with .done() but what you return and how you handle the result is up to you. Simplest option is to return a partial view so it's just a html fragment to insert into an existing div container.
.done(function(result) {
$("#someDiv").html(result);
});
I often return JSON with the view rendered as an html string { status: 0, message: "Success", html: "<div>..." }. You could omit the html fragment from the JSON if you just need a simple YES/NO.
public ActionResult SignInTrouble(some parameter...)
{
// some operation and validation here
return Json({
status: 1,
message: "Validation Error"
});
}
Then you get a few more possibilities with your response
.done(function(result) {
var status = result.status;
var message = result.message;
if (status == 0) {
// OK
} else {
// ERROR
}
$("#messageDiv").text(message);
}
It simply due to the page is reloaded, you must use ajax form in this case, so it'll only process the action of ajax form and then return the result to the form without reload the page
#Ajax.BeginForm("TroubleSubmit","Account", new AjaxOptions(){ ...... })

Reactive collection query in a template

I have a Template named movies, that has a method that returns a list of objects from a collection.
The query to generate that list of objects is created dynamically using data from another template method.
I would like to re-render the template, or just the components associated with that specific template method, whenever the filter data changes.
Here are the two methods used:
Template.movies.filter = function () {
if (Session.equals("filter", undefined)) {
return {};
}
return Session.get("filter");
};
Template.movies.movies = function () {
return Movies.find(Template.movies.filter(), {sort: [["Poster", "desc"]]});
};
On the HTML side it's a simple {{#each movies}}{{> movie}}{{/each}} to show the results from the movies method.
The problem is that when Session.get("filter") changes and therefore so does Template.movies.filter(), the HTML component relying on Template.movies.movies() data won't be updated with the new query results.
How would I achieve that behavior?
The easiest way is to just make a javascript function that both helpers utilize:
var getFilter = function() {
if (Session.equals("filter", undefined)) {
return {};
}
return Session.get("filter")
}
Template.movies.filter = function() { return getFilter(); }
Template.movies.movies = function () {
return Movies.find(getFilter(), {sort: [["Poster", "desc"]]});
};
This will react as you expect.

Backbone Collection.fetch() returns first item null

I'm using the following code in my view to fetch my collection from the server:
initialize: function () {
_this = this;
this.collection.fetch({
success : function(collection, response) {
_.each(response, function(i){
var todo = new TodosModel({
id: i.id,
content: i.content,
completed: i.completed
});
// Add to collection
_this.collection.add(todo);
// Render
_this.render(todo);
});
},
error : function(collection, response) {
console.log('ERROR GETTING COLLECTION!');
}
});
},
Which seems to work - here's the output from my server:
{
"0": {
"id": 1,
"content": "one",
"completed": false
},
"3": {
"id": 4,
"content": "two",
"completed": true
},
"4": {
"id": 5,
"content": "tester",
"completed": false
}
}
Except for the fact that if I log out my collection there is a null entry in the first position:
Which then causes issues as if I add an item it takes the ID of the last element. I'm new to backbone and am hoping I'm just missing something simple.
Here's my crack at a quick run through of your code. I haven't tested anything so there might be typos. I'm still not sure where the stray empty model is coming from but if you restructure your application as outlined below, I suspect the problem will go away.
The model and collection look okay so let us have a look at your view.
el: $('#todos'),
listBlock: $('#todos-list'),
newTodoField: $('#add input'),
//...
template: $('#todo-template').html(),
//...
events: { /* ... */ },
These should be okay but you need to ensure that all those elements are in the DOM when your view "class" is loaded. Usually you'd compile the template once:
template: _.template($('#todo-template').html()),
and then just use this.template as a function to get your HTML. I'll assume that template is a compiled template function below.
initialize: function () {
_this = this;
You have an accidental global variable here, this can cause interesting bugs. You want to say var _this = this;.
this.el = $(this.el);
Backbone already gives you a jQuery'd version of el in $el so you don't need to do this, just use this.$el.
this.collection.fetch({
success : function(collection, response) {
_.each(response, function(i) {
var todo = new TodosModel({ /* ... */ });
// Add to collection
_this.collection.add(todo);
// Render
_this.render(todo);
});
},
//...
The collection's fetch will add the models to the collection before the success handler is called so you don't have to create new models or add anything to the collection. Generally the render method renders the whole thing rather than rendering just one piece and you bind the view's render to the collection's "reset" event; the fetch call will trigger a "reset" event when it has fetched so the usual pattern looks like this:
initialize: function() {
// So we don't have to worry about the context. Do this before you
// use `render` or you'll have reference problems.
_.bindAll(this, 'render');
// Trigger a call to render when the collection has some stuff.
this.collection.on('reset', this.render);
// And go get the stuff we want. You can put your `error` callback in
// here if you want it, wanting it is a good idea.
this.collection.fetch();
}
Now for render:
render: function (todo) {
var templ = _.template(this.template);
this.listBlock.append(templ({
id: todo.get('id'),
content: todo.get('content'),
completed: todo.get('completed')
}));
// Mark completed
if(todo.get('completed')) {
this.listBlock.children('li[data-id="'+todo.get('id')+'"]')
.addClass('todo-completed');
}
}
Normally this would be split into two pieces:
render to render the whole collection.
Another method, say renderOne, to render a single model. This also allows you to bind renderOne to the collection's "add" event.
So something like this would be typical:
render: function() {
// Clear it out so that we can start with a clean slate. This may or
// may not be what you want depending on the structure of your HTML.
// You might want `this.listBlock.empty()` instead.
this.$el.empty();
// Punt to `renderOne` for each item. You can use the second argument
// to get the right `this` or add `renderOne` to the `_.bindAll` list
// up in `initialize`.
this.collection.each(this.renderOne, this);
},
renderOne: function(todo) {
this.listBlock.append(
this.template({
todo: todo.toJSON()
})
)
// Mark completed
if(todo.get('completed')) {
this.listBlock.find('li[data-id="' + todo.id + '"]')
.addClass('todo-completed');
}
}
Notice the use of toJSON to supply data to the template. Backbone models and collections have a toJSON method to give you a simplified version of the data so you might as well use it. The model's id is available as an attribute so you don't have to use get to get it. You could (and probably should) push the todo-completed logic into the template, just a little
<% if(completed) { %>class="completed"<% } %>
in the right place should do the trick.
addTodo: function (e) {
//...
var todo = new TodosModel({
id: todoID,
content: todoContent,
completed: todoCompleted
});
this.render(todo);
todo.save();
_this.collection.add(todo);
You could bind renderOne to the collection's "add" event to take care of rendering the new model. Then use the save callbacks to finish it off:
var _this = this;
var todo = new TodosModel({ /* ... */ });
todo.save({}, {
wait: true,
success: function(model, response) {
// Let the events deal with rendering...
_this.collection.add(model);
}
});
Again, an error callback on the save might be nice.
completeTodo: function (e) {
//...
todo.save({
completed: todoCompleted
});
}
The save call here will trigger a 'change:completed' event so you could bind to that to adjust the HTML.
removeTodo: function (e) {
//...
}
The destroy call will trigger a "destroy" event on the model and on the collection:
Any event that is triggered on a model in a collection will also
be triggered on the collection directly, for convenience. This
allows you to listen for changes to specific attributes in any model
in a collection, [...]
So you could listen for "destroy" events on the collection and use those to remove the TODO from the display. And destroying the model should remove it from the collection without your intervention.
printColl: function () {
this.collection.each(function (todo) {
console.log('ID: '+todo.get('id')+' | CONTENT: '+todo.get('content')+' | COMPLETED: '+todo.get('completed'));
});
}
You could just console.log(this.collection.toJSON()) instead,
you'd have to click around a little to open up the stuff in the
console but you wouldn't miss anything that way.
All the event binding for the collection would take place in your
view's initialize method. If you're going to remove the view then
you'd want to override the remove to unbind from the collection
to prevent memory leaks:
remove: function() {
// Call this.collection.off(...) to undo all the bindings from
// `initialize`.
//...
// Then do what the default `remove` does.
this.$el.remove()
}
You could also use a separate view for each TODO item but that might be overkill for something simple.

Resources