I'm coming to a part in my MVC 3 page where I need to do a JQuery $.Ajax callback, but unlike before where I have returned some simple values and handled updating the UI using JQuery I need to refresh the part of the page that displays the main ViewModel data. So in effect it's almost as if I need to do a callback but instead of returning the JSonResult I want to return the original View?? I'm pretty sure though that I need to be thinking about using partial views? Could anyone advise or perhaps point me towards a good tutorial?
Thanks in advance.
If I understand correctly. In this sort of scenario I usually re-use the same action but return either a full or partial view based on the IsAjaxRequest method.
public ActionResult MyAction(string someParam)
{
//...
if (Request.IsAjaxRequest())
{
return PartialView(model);
}
else
{
return View(model);
}
}
This can then be called in jQuery using something like:
$("a.myAction").click(function (event)
{
event.preventDefault();
var button = $(this);
// Get more results using ajax
$.get(button.attr("href"), function (data)
{
// Add the new content
$('div#myActionResult').empty().html(data);
}, "html");
}
You may need to POST instead or change the Url to include a query string if you want to send data to the action to change the view.
Related
I have controller methods that do stuff with WMI classes (waking selected machine, opening or closing processes, initiating an RDP session) and then reload the current view. To reload I've tried the following:
return View();
return RedirectToAction("Index", "RenderNodesController");
return RedirectToAction("Index");
return Redirect(Request.UrlReferrer.ToString());
The methods all execute successfully with regards to performing the WMI calls, but the return statement doesn't reload the page. If I refresh the page manually it reloads and displays changed information as expected. Also rather weirdly if I step through the return statements in the debugger I see that the view's Index method executes so debugging seems not to help in this instance.
I'd be grateful for any help.
Just wanted to add some explanation to this answer:
Because you're performing an AJAX request, it won't reload the user's browser page on success, all it will do is return the results of the action method called within the result parameter. $.post is a shorthand version of $.ajax so the behaviour will be the same.
Solution:
Use for example location.reload(true) (as you did).
I still don't know why
return View();
and similar doesn't execute but in the end I got it working using Javascript's window.location.reload(), full script below for completeness:
<script type="text/javascript">
$(document).ready(function () {
$('.checkbox').click(function () {
var url = '#Url.Action("CallProcess", "RenderNodes")';
var networkname = $(this).data("networkname");
var processname = $(this).data("processname");
var filename = $(this).data("filename");
var start = $(this).is(':checked');
$.post(url, { NetworkName: networkname, ProcessName: processname, FileName: filename, Start: start },
function (data) {
$("#CallProcess").html(data);
});
window.location.reload(true);
})
});
</script>
I had the same problem, my solution is a little tricky, but it works. Instead of using a
return View("/MyRedirectView.cshtml");
in the controller, I used a
return RedirectToAction("MyRedicrectAction");
and the action is this:
[AllowAnonymous]
public ActionResult MyRedicrectAction()
{
return View("/MyRedirectView.cshtml");
}
Using MVC2 I have created a form using the Ajax helper in a view. The form posts to a controller which binds to a model object. A PartialViewResult is returned by the controller and the HTML gets updated into a div. So far, so good.
I now need to submit the same form and return the results in a generated file for the user to download. Obviously I don't want the file contents going into my div.
Is there an elegant way to handle this situation without having to hack it to bits? I'm fairly new to MVC / AJAX and it's still a point of confusion for me.
You may not use ajax call to download files. Following links may help you to do what you are trying to do
JQuery Ajax call for PDF file download
http://forums.asp.net/t/1683990.aspx/1
OK, so I couldn't find any simple solutions anywhere so I came up with my own. I remove the Ajax event handlers from the form when I want the download, put them back when I want the Ajax. I'm guessing there's a more elegant way to do this, as this feels like a 'clever trick'. I'm open to better suggestions but so far this is my preferred method.
Reference ToggleAjax.js on my page:
var ToggleAjax = function ($, form) {
var onclick = form.onclick,
onsubmit = form.onsubmit;
$('input[class*="ajax-enabled"]').click(function () {
form.onclick = onclick;
form.onsubmit = onsubmit;
});
$('input[class*="ajax-disabled"]').click(function () {
form.onclick = function () { };
form.onsubmit = function () { };
});
};
Then I call ToggleAjax on my page and pass in the form:
$(function () {
ToggleAjax($, $('form')[0]);
});
And of course I add the class ajax-enabled or ajax-disabled to the input controls.
I have a Generic list in my Model class. I want to have a textbox with autocomplete in my view which fills data from the generic list. How can I do this?.
For this you will need
Function on server side which will return list of matching data and will accept string entered by the user.
Something like this
public JsonResult AutoComplete(string input)
{
//Your code goes here
}
In the View, for the text box you need to bind KeyDown event. You can take help of jQuery for this. On key down handler you will make an Ajax call to the function you have defined in the Controller. Some thing like this:
$.ajax({
url: '#Url.Action("AutoComplete", "ControllerName")',
data: 'input=' + sampleInput,
success: function (data) {
//Show the UL drop down
},
error: function (data) {
// Show Error
}
});
In response you will get list of strings, which you will need to bind to some html element like "UI". Once done, display this UI with proper CSS below the text box. Using jQuery, you can retrieve the pixel location of text box too.
You can not use Asp.Net Auto Complete box in your project as you are developing app in MVC (no viewstate). I hope you get the idea.
You can use JQuery Autocomplate.
To fill the list, you can populate the data from you object.
I can't remember the exact Razor syntax, but you can refer to this:
//data is your Model object of type List<String>
var listString = [#foreach(x in data) { '#x',}];
$( "#dataList" ).autocomplete({
source: listString
});
<input id="dataList">
JQuery Autocomplte
http://jqueryui.com/demos/autocomplete/
This is client side auto complete, I can provide server side if you need.
I have a Partial View that renders WebGrid. My controller looks like
public ActionResult Index()
{
return View();
}
public ActionResult GetUserList(int? page, string sort, string sortdir)
{
var model = UserModel.getList(page,sort,sortdir);
return PartialView("_UserList",model);
}
Index.cshtml :
....
#Html.Action("GetUserList")
The problem is that every time I click on grid navigation or sort links it calls Index method. How can I make Webgrid to execute a different action (GetUserList in this case)? I'm sure I can prepend GetUserList to all links in grid using jquery, but I believe it should be a better way.
It's also possible that what I'm doing is completely wrong, so thanks for your suggestions.
After lot of monkeying around and digging (and even fiddling with Reflector with WebGrid's source code), I came to the conclusion that with WebGrid, you cannot control/change the Header link action.
To create the header link URL, the path is taken from HttpContext.Request.Path, so there is no way to customize it to point to a different route.
One very ugly hack would be to tap into to jQuery Ajax's events (since the header link uses jQuery.load to sort) and overwrite the URL:
Album Id
Better solution would be to use:
Telerik Grid which lets you specify custom routes and also offers much more flexibility in rendering your layout
or MvcContrib Grid (not sure if this lets you modify header links but definitely offers more flexibility than WebGrid)
#MrChief had the idea above about the ugly hack...I put that together. Here is the main code that I used to do this. It does, indeed, hijack the ajax call before it is put on the wire. The key is to modify the URL that is getting sent because the grid will grab that URL from HttpContext.Request.Path. and plug it into the onclick for the anchor element.
I put this into my main common.js and will simply attach a function to capture the ajaxSend event which happens just before the data is sent.
// Used to hijack the sending of all AJAX calls. Before it sends the call to the server, it checks to see if the
// active element (the element that prompted the call) is marked with a given class. If so, then it will perform
// the given operation.
$(document).ajaxSend(function (event, jqXHR, ajaxOptions) {
var activeElement = document.activeElement;
if ($(activeElement).attr('redosorturl') != null) {
// If this is a sort anchor link from a grid that needs to have the sort link redone, do it here.
// the code is in the eipGrip.js file.
if ($(activeElement).attr('redosorturl').toString() == 'redoSortURL') {
var newURL = RedoGridSortURL(activeElement, ajaxOptions.url.toString());
ajaxOptions.url = newURL.toString();
}
}
return false;
});
When rendering the page, I have marked the tag in column header that contains the incorrect URL with a class named "redosorturl', so I know when I hijack the ajax call, the operation has to be done on this element. I then call a custom function that gives me the correct URL, then the ajaxOptions.url is then rewritten with that new URL.
I have to pass the activeElement to that rewrite function so I can traverse up the DOM to get the grid information, where I have put data like the controller and action method that is used along with and IDs and other info that I use for the URL. Likewise, I pass in the current url string because the grid will inject a token at the end of the url that I parse off and put on the new url.
Your conclusion isn't right. You just need to wrap your webgrid in a Get form:
using (Html.BeginForm("GetUserList", "ThingaMaBob", System.Web.Mvc.FormMethod.Get))
{
var grid = new WebGrid(
...
));
Html.Hidden(grid.SortFieldName, grid.SortColumn);
Html.Hidden(grid.SortDirectionFieldName, grid.SortDirection == SortDirection.Ascending ? "ASC" : "DESC");
}
The hiddens are so that the sort dir and sort field end up in parseable form in the querystring. You end up with urls like localhost/ThingaMaBob/GetUserList?someotherfields=whatever=&sort=city&sortdir=ASC
If you remove [HttpPost] attribute and let the route come to the same function. you'll find the Request["page"] value in your method. this will allow you to put a check on Request["Page"] value.
I am making an Ajax call and adding content to a form inside a MVC2 app.
I need to update the Client Validation Metadata with the validation for my new content.
<script type="text/javascript">
//<![CDATA[
if (!window.mvcClientValidationMetadata) { window.mvcClientValidationMetadata = []; }
window.mvcClientValidationMetadata.push({"Fields":[{"
...
</script>
Is there a way to generate this metadata for a partial view ?
Thanks in advance.
I was banging my head against a wall for a few days on this too and was going to go down the route of removing the form tag, but have just got it working in a slightly less hacky way if you are still interested. My scenario was similar in that I have a form with a collection of elements to validate initially, but users can dynamically add new rows via ajax.
I'll break it down so hopefully it'll be easier to see what is going on. Looking at the MVC source code, the form and validation works roughly as so:
Html.BeginForm() outputs the opening form tag then creates and returns a new instance of MvcForm, which doesn't outwardly do much except make the scope of the form easier to manage for you.
It does however create a new FormContext and stores this within ViewContext.FormContext. It is this FormContext that tracks the client validation.
The last thing Html.BeginForm() does is set the FormId property of the new FormContext, using the id of the form tag. This is required so the client script can match up forms and validation rules.
Html.EndForm() disposes the MvcForm. This Dispose method outputs the form closing tag and then calls ViewContext.OutputClientValidation() which is resposible for outputting the javascript. Lastly it removes the current FormContext and sets it back to the parent FormContext or null if there isn't one.
So to not output the form tag we somehow need to take some of the FormContext management out of the MvcForm constructor/destructor.
So within my Partial View I did the following:
At the top I check if the ViewContext.FormContext has a value. If so we we are in the initial load so no need to mess around. If not, it is an ajax call, so I enable client validation, create a new MvcForm directly (not with BeginForm) - this causes a FormContext to be created - and set the FormContext.FormId to the same as my parent page
At the end of the view, I check if I have a form instance and if so, call ViewContext.OutputClientValidation() and reset the ViewContext.FormContext to null. I do not Dispose() the MvcForm as this would output the closing tag and MvcForm does not contain disposable objects.
The skeleton of the view looks as so:
<%
MvcForm dummyForm = null;
if (this.ViewContext.FormContext == null)
{
Html.EnableClientValidation();
dummyForm = new MvcForm(this.ViewContext);
this.ViewContext.FormContext.FormId = "mainform";
}
%>
// standard partial view markup goes here
<%
if (dummyForm != null)
{
this.ViewContext.OutputClientValidation();
this.ViewContext.FormContext = null;
}
%>
You could quite easily wrap this up into an extension method
Phil
Finally got it to work.
The answer is simple: don't waist time with MicrosoftMvcValidation.js. It is generated with Script# which makes it difficult to extend.
Switch to xVal and jQuery Validation.
It doesn't need a form to generate the client validation metadata.
Also in order to load validation for a AJAX request all you have to do is to call the following after you have the new Html:
lForm.find("#placeholder").empty();
lForm.valid();
lForm.find("#placeholder").html(responseHtml);
That does it. First you remove the old content. Than re-run validation to get rid of potentially obsolete validation errors. Than add the new content. Works like a cham.
Also jQuery Validation makes it really easy to enable or disable validation for a certain field (conditional validation).
I have the same problem and resolve using the Future files, and in MicrosoftMvcJQueryValidation.js I change the and of file, this:
$(document).ready(function () {
var allFormOptions = window.mvcClientValidationMetadata;
if (allFormOptions) {
while (allFormOptions.length > 0) {
var thisFormOptions = allFormOptions.pop();
__MVC_EnableClientValidation(thisFormOptions);
}
}
});
for:
function chargeValidation() {
var allFormOptions = window.mvcClientValidationMetadata;
if (allFormOptions) {
while (allFormOptions.length > 0) {
var thisFormOptions = allFormOptions.pop();
__MVC_EnableClientValidation(thisFormOptions);
}
}
}
and in content after close form using I call the 'chargeValidation()', this resolve for me the problem I have using $.get(action) containing a form validation.
I hope to help you!
Finally found it.
After content is loaded in dynamically you will need to register the new form.
Since I am using Facebox, I added it to the facebox code, however you can add it wherever you need, or in a callback if your modal or whatever you are loading into has an afterLoaded event.
I wrapped them in a try/catch just in case i ever use facebox without the validation stuff.
Just run these two lines AFTER your content has been loaded:
try {
Sys.Application.remove_load(arguments.callee);
Sys.Mvc.FormContext._Application_Load();
} catch (err) {/* MVC Clientside framework is likely not loaded*/ }
I made some progress but I am not quite happy.
Problem #1: The client validation metadata is not generated unless you have a Html.BeginForm() in your partial. Which in my case is false because I do not update the entire form, I update portions of it.
Solution for Problem #1: Add a form in the partial view, let MVC generate the client validation MetaData and remove the form tags with a action filter. Let's call this Hack #1.
public class RemoveFormFilterAttribute : ActionFilterAttribute
{
private static readonly MethodInfo SwitchWriterMethod = typeof(HttpResponse).GetMethod("SwitchWriter", BindingFlags.Instance | BindingFlags.NonPublic);
private TextWriter _OriginalWriter;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
_OriginalWriter = (TextWriter)SwitchWriterMethod.Invoke(HttpContext.Current.Response, new object[] {new HtmlTextWriter(new StringWriter())});
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (_OriginalWriter != null)
{
HtmlTextWriter lTextWriter =(HtmlTextWriter) SwitchWriterMethod.Invoke(HttpContext.Current.Response, new object[] {_OriginalWriter});
string lOriginalHTML = lTextWriter.InnerWriter.ToString();
string lNewHTML = RemoveFormTags(lOriginalHTML);
filterContext.HttpContext.Response.Write(lNewHTML);
}
}
Problem #2: The initial client validation metadata for the page is gone by the time I have the metaData for the new content.
Solution for Problem #2: Store the initial metadata (hard copy) and update it with the new fieds, than call the methods you mentioned to let MVC know new stuff arrived. Let's call this Hack #2.
<script type="text/javascript">
var pageMvcClientValidationMetadata;
$(document).ready(function() {
$("input[name='PaymentTypeName']").change(PaymentTypeChanged);
//create a back-up of the ValidationMetadata
pageMvcClientValidationMetadata = JSON.parse(JSON.stringify(window.mvcClientValidationMetadata));
});
function PaymentTypeChanged() {
var selectedPaymentType = $("input[name='PaymentTypeName']:checked").val();
$.ajax(
{
url: 'PersonalData/GetPaymentTypeHtml?&paymentType=' + selectedPaymentType,
type: "GET",
cache: false,
success: GetPaymentTypeHtml_Success
});
}
function GetPaymentTypeHtml_Success(result) {
$('#divPaymentTypeDetails').html(result);
UpdateValidationMetaData();
}
function UpdateValidationMetaData() {
//update the ValidationMetadata
for (i = 0; i < window.mvcClientValidationMetadata[0].Fields.length; i++) {
pageMvcClientValidationMetadata[0].Fields.push(window.mvcClientValidationMetadata[0].Fields[i]);
}
//restore the ValidationMetadata
window.mvcClientValidationMetadata = JSON.parse(JSON.stringify(pageMvcClientValidationMetadata));
//Notify the Validation Framework that new Metadata exists
Sys.Application.remove_load(arguments.callee);
Sys.Mvc.FormContext._Application_Load();
}
Now. Any improvements would be appreciated.
Hack #1: How can I generate the client validation metadata without having an actual form ?
HAck #2: How can I appent to the page validation metadata ?