I wanted to use OnComplete, since I needed to modify the view before it was updated with the data from the Ajax call. As it says here: MSDN, it should be pretty straight forward.
However, it didn't work and after some investigation it seems that OnComplete fires before OnSucess but after the view is updated.
Code used for testing:
#{
AjaxOptions options = new AjaxOptions {OnSuccess = "onSuccess",
OnComplete = "onComplete", UpdateTargetId = "Update"};
}
#Ajax.ActionLink("Hit it", "Action", options)
<div id="Update"></div>
<script type="text/javascript">
function onSuccess() {
alert('onSuccess: ' + $('#Update').html());
}
function onComplete() {
alert('onComplete ' + $('#Update').html());
}
</script>
public ContentResult Action()
{
return Content("Content");
}
Am I missing something here or what's up?
Try this in your View:
#Ajax.ActionLink("Hit it", "Action", new AjaxOptions() { OnSuccess="done"} )
<div id="Update"></div>
Controller Action:
public ContentResult Action()
{
return Json(new { content="content" }, JsonRequestBehavior.AllowGet);
}
JavaScript:
function done(data) {
var message = data;
if (typeof message["content"] !== "undefined") {
$('#Update').html(message["content"]);
} else {
alert("error");
}
}
You can pass a Json result from your Controller and get that message in your View via JavaScript and update your div.
The issue is that the UpdateTargetId is actioned before the OnSuccess handler is triggered.
If you want to to update the view before or perform extra steps before the content is updated, then remove the updatetargetid from the AjaxOptions and handle the update yourself in the OnSuccess handler. You will get full control over when the view is updated then.
function onSuccess(result, status, xhr)
{
// put in your view update code here.
// and then
$("#Update").html(result);
}
OnComplete is fired when ajax call completed and response data has been instantiated but page has not yet updated
Related
I have the following in my view
#using (Ajax.BeginForm("Search", "Home", null,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
UpdateTargetId = "gridContent",
}, new { #class = "search" }))
{
<input type="submit" value="Search" />
}
<div id="gridContent">
</div>
This is what returns /Home/Search
#model List<TestTable.Models.People>
#{
Layout = null;
}
#{
var grid = new WebGrid(Model, canPage: true, canSort: true, rowsPerPage: 5, ajaxUpdateContainerId: "tableDiv"); grid.Pager(WebGridPagerModes.NextPrevious);
}
<div id="tableDiv">
#grid.GetHtml(
columns: grid.Columns(
grid.Column("Name", " Name")
))
</div>
This works good in MVC3, however MVC4 sends a script on every new search,
causing one new additional request for each submit button click for every paging and sorting query.
Here is how it looks:
"http://localhost:59753/Home/Search".
"http://localhost:59753/Home/Search?sort=Name&sortdir=ASC&__swhg=1394297281115"
"http://localhost:59753/Home/Search".
"http://localhost:59753/Home/Search?sort=Name&sortdir=ASC&__swhg=1394297284491"
"http://localhost:59753/Home/Search?sort=Name&sortdir=ASC&__swhg=1394297284490"
Any ideas how to fix that?
Thanks in advance!
The reason this is happening is because the WebGrid control injects the following script into your DOM every time you render it (in your case every time you submit the AJAX form because the WebGrid is situated in a partial that you are injecting in your DOM):
<script type="text/javascript">
(function($) {
$.fn.swhgLoad = function(url, containerId, callback) {
url = url + (url.indexOf('?') == -1 ? '?' : '&') + '__swhg=' + new Date().getTime();
$('<div/>').load(url + ' ' + containerId, function(data, status, xhr) {
$containerId).replaceWith($(this).html());
if (typeof(callback) === 'function') {
callback.apply(this, arguments);
}
});
return this;
}
$(function() {
$('table[data-swhgajax="true"],span[data-swhgajax="true"]').each(function() {
var self = $(this);
var containerId = '#' + self.data('swhgcontainer');
var callback = getFunction(self.data('swhgcallback'));
$(containerId).parent().delegate(containerId + ' a[data-swhglnk="true"]', 'click', function() {
$(containerId).swhgLoad($(this).attr('href'), containerId, callback);
return false;
});
})
});
function getFunction(code, argNames) {
argNames = argNames || [];
var fn = window, parts = (code || "").split(".");
while (fn && parts.length) {
fn = fn[parts.shift()];
}
if (typeof (fn) === "function") {
return fn;
}
argNames.push(code);
return Function.constructor.apply(null, argNames);
}
})(jQuery);
</script>
This script is baked into the WebGrid helper and there's not much you could do against it once you enable AJAX on your WebGrid. In this script you will undoubtedly notice how it subscribes to the click event of the pagination anchors in a lively manner:
$(containerId).parent().delegate(containerId + ' a[data-swhglnk="true"]', 'click', function() {
$(containerId).swhgLoad($(this).attr('href'), containerId, callback);
return false;
});
which is all sweet and dandy except that every time you click on the submit button you are injecting this script into your DOM (because your WebGrid is in the partial) and basically you are subscribing to the click event of the pagination anchors multiple times.
It would have been great if the authors of this WebGrid helper have left you the possibility to replace this delegate with a standard click handler registration which would have been ideal in this case as it wouldn't create multiple event registrations, but unfortunately the authors didn't left you with this possibility. They just assumed that the WebGrid would be part of the initial DOM and thus their script.
One way would be to subscribe to the OnBegin handler of the Ajax form submission and simply undelegate the existing event handlers because they will be overwritten once you refresh the DOM:
#using (Ajax.BeginForm("Search", "Home", null,
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
OnBegin = "callback",
HttpMethod = "POST",
UpdateTargetId = "gridContent",
}, new { #class = "search" }))
{
<input type="submit" value="Search" />
}
<div id="gridContent"></div>
<script type="text/javascript">
var callback = function (a) {
$('#tableDiv').parent().undelegate('#tableDiv a[data-swhglnk="true"]', 'click');
};
</script>
But to be honest, personally I just hate all this automatically generated scripts and simply never use any Ajax.* helpers stuff as well as activating AJAX on the WebGrid. I prefer to unobtrusively AJAXify the elements I want using jQuery which provides me with far greater control over what's happening. This way I would simply have externalized the bunch of automatically generated javascript by the WebGrid helper into a separate js file that I would have included in my View and there wouldn't be any needs of unregistering and cleaning the mess of the duplicate event handlers created by following the standard way of doing things.
A bit late but I had a similar problem and couldn't find any description of it so thought I'd add it here in case it can help somebody.
No matter what I tried the sorting and paging requests were always duplicated. I tried fixes as described here but even before I had done any type of AJAX update I got the duplication.
I put that problem on hold and after failing to style the pagination to my satisfaction I created my own as a partial view. When deleting the old pagination the duplication was no more..... Haven't taken the time to try and figure out why, just happy it is solved.
So I removed this:
#grid.Pager(mode: WebGridPagerModes.All, firstText: "First", previousText: "Prev", nextText: "Next", lastText: "Last")
As I said, in case it helps someone.
Looks like the paging and sorting links are bound using "on"/"live" event every time the grid is rendered. It could be solved unbinding the events of the elements of the grid before rendering the grid html or on the ajaxUpdateCallback method.
$('#tableDiv').andSelf().unbind();
I have solved this for MVC 5, you would need to use ajax call rather than using the ajax form, catch the ajax response and replace the partial page's DOM generated by the webgrid helper using below:
var data = data.replace('<script type="text/javascript">', '<script type="text/javascript"> $(".table").undelegate();');
$('#YourParentDivIDWherePartialIsRendered').undelegate();
$.ajax
(
{
contentType: "application/json; charset=utf-8",
type: 'POST',
url: '/YourController_Name/YourAction_Name',
data: JSON.stringify(YourModel),
success: function (data) {
//Added to undelegate the old events tagged to the partial view's grid.
var data = data.replace('<script type="text/javascript">', '<script type="text/javascript"> $(".table").undelegate();');
$('#YourParentDivIDWherePartialIsRendered').undelegate();
$('#accountSearch-grid').html(data);
$(document).foundation();
},
error: function (xhr, status, error) {
alert(error);
}
});
put this script to your Index.cshtml or js file
<script type="text/javascript">
(function($) {
$.fn.swhgLoad = function(url, containerId, callback) {
url = url + (url.indexOf('?') == -1 ? '?' : '&') + '__swhg=' + new Date().getTime();
$('<div/>').load(url + ' ' + containerId, function(data, status, xhr) {
$containerId).replaceWith($(this).html());
if (typeof(callback) === 'function') {
callback.apply(this, arguments);
}
});
return this;
}
$(function() {
$('table[data-swhgajax="true"],span[data-swhgajax="true"]').each(function() {
var self = $(this);
var containerId = '#' + self.data('swhgcontainer');
var callback = getFunction(self.data('swhgcallback'));
$(containerId).parent().delegate(containerId + ' a[data-swhglnk="true"]', 'click', function() {
$(containerId).swhgLoad($(this).attr('href'), containerId, callback);
return false;
});
})
});
function getFunction(code, argNames) {
argNames = argNames || [];
var fn = window, parts = (code || "").split(".");
while (fn && parts.length) {
fn = fn[parts.shift()];
}
if (typeof (fn) === "function") {
return fn;
}
argNames.push(code);
return Function.constructor.apply(null, argNames);
}
})(jQuery);
then, processing your grid html string in class
string html = _grid.GetHtml(
columns: _columns,
...
).ToHtmlString();
Regex reg1 = new Regex("<script(.|\n)*?</script>", RegexOptions.IgnoreCase);
string _script = reg1.Match(html).Value.ToString();
html = html.Replace(_script, "");
in the index file:
#MvcHtmlString.Create(#html)
that' all
Actually the solution $('#tableDiv').parent().off('click', '#tableDiva[data-swhglnk="true"]'); is working perfectly but it remains the __swhg in the call URL of pagination so here is the code for removing the extra __swhg in the call of page using AJAX.
$(document).ajaxComplete(function () {
$('a[data-swhglnk="true"]').click(function () {
$(this).attr("href", $(this).attr("href").replace(/(^|&)__swhg=([^&]*)/, ''));
});
});
If anyone is going through these and still having problems, I believe I found the real issue. The script was never being rendered twice on the page for me so the accepted answer didn't make sense.
The issue was instead with this line:
$('table[data-swhgajax="true"],span[data-swhgajax="true"]').each(function() {
If the pager is not in the footer of the table (by defining it in the setup of the webgrid) and is instead defined by calling grid.pager() it will put the pager in a span. This means when the above line is called it binds the click event to the parent of the table (gridContent) and to the parent of the span (gridContent).
There are a few options, but what I opted to do was essentially what top answer said, and remove the delegates for that element like so:
$("#gridContent").off("click", "**");
And then rebind the same exact click function, but only bind it to the span. So the line referenced above I changed to:
$('span[data-swhgajax="true"]').each(function () {
And it works as intended. This will of course break any pagers on the same page that are part of the table.
First Remove ajax Update Callback from Web Grid and add following java script code below to web grid or web grid Container div:
$("#WebgridContainerDiv table a").click(function (event) {
event.preventDefault();
var href = $(this).attr("href");
$.ajax({
url: href,
dataType: 'html',
success: function (data) {
$("#WebgridContainerDiv").empty();
$("#WebgridContainerDiv").html(data);
}
})
});
I have a form in which I need to call two action methods, one after the other. This is how the flow goes.
First I check if the prerequisite data is entered by the user. If not then I show a message that user needs to enter the data first.
If all the prerequisite data is entered, I call an action method which return data. If there is no data returned then I show a message "No data found" on the same page.
If data is returned then I call another action method present in a different controller, which returns a view with all the data, in a new tab.
The View:
#using (Ajax.BeginForm("Index", "OrderListItems", null, new AjaxOptions { OnBegin = "verifyRequiredData"}, new { #id = "formCreateOrderListReport", #target = "_blank" }))
{
//Contains controls and a button
}
The Script in this View:
function verifyRequiredData() {
if ($("#dtScheduledDate").val() == "") {
$('#dvValidationSummary').html("");
var errorMessage = "";
errorMessage = "<span>Please correct the following errors:</span><ul>";
errorMessage += "<li>Please enter Scheduled date</li>";
$('#dvValidationSummary').append(errorMessage);
$('#dvValidationSummary').removeClass('validation-summary-valid').addClass('validation-summary-errors');
return false;
}
else {
$('#dvValidationSummary').addClass('validation-summary-valid').removeClass('validation-summary-errors');
$('#dvValidationSummary').html("");
$.ajax({
type: "GET",
url: '#Url.Action("GetOrderListReport", "OrderList")',
data: {
ScheduledDate: $("#dtScheduledDate").val(),
Crews: $('#selAddCrewMembers').val(),
Priorities: $('#selPriority').val(),
ServiceTypes: $('#selServiceTypes').val(),
IsMeterInfoRequired: $('#chkPrintMeterInfo').val()
},
cache: false,
success: function (data) {
debugger;
if (data !== "No data found") {
//var newUrl = '#Url.Action("Index", "OrderListItems")';
//window.open(newUrl, '_blank');
return true;
} else {
//Show message "No data found"
return false;
}
}
});
return false;
}
}
The "GetOrderListReport" Action method in "OrderList" Controller:
public ActionResult GetOrderListReport(OrderListModel model)
{
var contract = new OrderReportDrilldownParamDataContract
{
ScheduledDate = model.ScheduledDate
//Setting other properties as well
};
var result = OrderDataModel.GetOrderList(contract);
if (string.IsNullOrWhiteSpace(result) || string.IsNullOrEmpty(result))
{
return Json("No data found", JsonRequestBehavior.AllowGet);
}
var deserializedData = SO.Core.ExtensionMethods.DeserializeObjectFromJson<OrderReportDrilldownDataContract>(result);
// send it to index method for list
TempData["DataContract"] = deserializedData;
return Json(deserializedData, JsonRequestBehavior.AllowGet);
}
The last action method present in OrderListItems Controller, the result of which needs to be shown in a new tab:
public ActionResult Index()
{
var deserializedData = TempData["DataContract"] as OrderReportDrilldownDataContract;
var model = new OrderListItemViewModel(deserializedData);
return View(model);
}
The problem is that I am not seeing this data in a new tab, although I have used #target = "_blank" in the Ajax.BeginForm. I have also tried to use window.open(newUrl, '_blank') as can be seen above. But still the result is not shown in a new tab.
Please assist as to where I am going wrong?
If you are using the Ajax.BeginForm you shouldn't also be doing an ajax post, as the unobtrusive ajax library will automatically perform an ajax post when submitting the form.
Also, if you use a view model with data annotation validations and client unobtrusive validations, then there would be no need for you to manually validate the data in the begin ajax callback as the form won't be submitted if any validation errors are found.
The only javascript code you need to add in this scenario is a piece of code for the ajax success callback. That will look as the one you currently have, but you need to take into account that opening in new tabs depends on the browser and user settings. It may even be considered as a pop-up by the browser and blocked, requiring the user intervention to allow them as in IE8. You can give it a try on this fiddle.
So this would be your model:
public class OrderListModel
{
[Required]
public DateTime ScheduledDate { get; set; }
//the other properties of the OrderListModel
}
The form will be posted using unobtrusive Ajax to the GetOrderListReport of the OrderList controller. On the sucess callback you will check for the response and when it is different from "No data found", you will then manually open the OrderListItems page on a new tab.
This would be your view:
#model someNamespace.OrderListModel
<script type="text/javascript">
function ViewOrderListItems(data){
debugger;
if (data !== "No data found") {
var newUrl = '#Url.Action("Index", "OrderListItems")';
//this will work or not depending on browser and user settings.
//passing _newtab may work in Firefox too.
window.open(newUrl, '_blank');
} else {
//Show message "No data found" somewhere in the current page
}
}
</script>
#using (Ajax.BeginForm("GetOrderListReport", "OrderList", null,
new AjaxOptions { OnSucces= "ViewOrderListItems"},
new { #id = "formCreateOrderListReport" }))
{
#Html.ValidationSummary(false)
//input and submit buttons
//for inputs, make sure to use the helpers like #Html.TextBoxFor(), #Html.CheckBoxFor(), etc
//so the unobtrusive validation attributes are added to your input elements.
//You may consider using #Html.ValidationMessageFor() so error messages are displayed next to the inputs instead in the validation summary
//Example:
<div>
#Html.LabelFor(m => m.ScheduledDate)
</div>
<div>
#Html.TextBoxFor(m => m.ScheduledDate, new {id = "dtScheduledDate"})
#Html.ValidationMessageFor(m => m.ScheduledDate)
</div>
<input type="submit" value="Get Report" />
}
With this in place, you should be able to post the data in the initial page using ajax. Then based on the response received you will open another window\tab (as mentioned, depending on browser and user settings this may be opened in a new window or even be blocked) with the second page content (OrderListItems).
Here's a skeleton of what I think you are trying to do. Note that window.open is a popup though and most user will have popups blocked.
<form id="formCreateOrderListReport">
<input type="text" vaule="testing" name="id" id="id"/>
<input type="submit" value="submit" />
</form>
<script type="text/javascript">
$('#formCreateOrderListReport').on('submit', function (event) {
$.ajax({
type: "POST",
url: '/home/test',
data: { id: $('#id').val()},
cache: false
}).done(function () {
debugger;
alert("success");
var newUrl = '/home/contact';
window.open(newUrl, '_blank');
}).fail(function () {
debugger;
alert("error");
});
return false;
});
</script>
Scale down the app to get the UI flow that you want then work with data.
I have simple ajax form in MVC. In AjaxOptions there is OnComplete set to simple javascript function which does one thing - returns false.
#using (Ajax.BeginForm("Action", "Controller", new AjaxOptions { UpdateTargetId = "DivFormId", HttpMethod = "Post", OnComplete = "preventUpdate" }))
function preventUpdate(xhr) {
return false;
}
The problem is, that page is already updated. E.g. in one case controller returns partial view after postback, in other case it returns some Json object. I want it to update page when partial view is returned, and to show dialog window when json is returned. Unfortunately when json is returned, it clears the page (update it with json) even when OnComplete function returns false as MSDN says: To cancel the page update, return false from the JavaScript function.
How to prevent page update depending on received response?
Thank you!
----- UPDATE -------
So far I found following solution. When I don't specify UpdateTargetId, I can do manually with the response what I want. But it is still not documented behaviour with return false.
Use OnSuccess and get rid of UpdateTargetId. Like this:
#using (Ajax.BeginForm("Action", "Controller", new AjaxOptions { HttpMethod = "Post", OnSuccess = "foo" }))
{
...
}
and then:
function foo(result) {
if (result.SomePropertyThatExistsInYourJsonObject) {
// the server returned a JSON object => show the dialog window here
} else {
// the server returned a partial view => update the DOM:
$('#DivFormId').html(result);
}
}
I'm new to ASP.NET MVC(-4).
I want to make an Ajax call from my website using jquery and fill in a div on the page using the returned html. Since it is only a div I do not need a full html page with header and full body and stuff.
What should be on the receiving side?
Should it be a normal view, a partial view, some special type of resource or handler or some other magic?
You can use this With Post and Get operaitons
Script
$.ajax({
url: '#Url.Action("SomeView")',
type: 'GET',
cache: false,
data: { some_id: id},
success: function(result) {
$('#container').html(result);
}
});
Controller
public ActionResult SomeView(int some_id)
{
....
return PartialView();
}
View
<div id="container">
#Html.Partial("SomeViewPartial")
</div>
OR you can use AjaxActionLink
View
#Ajax.ActionLink("text", "action", "controller",
new AjaxOptions
{
InsertionMode = InsertionMode.Replace,
UpdateTargetId = "container",
OnSuccess = "onSuccess",
})
Script
function onSuccess(result) {
alert(result.foo);
}
Controller
public ActionResult SomeView(int some_id)
{
return Json(new { foo = "bar" }, JsonRequestBehavior.AllowGet);
}
Also You can use Ajax.ActionLink to update only content page. with using this:
In ~/Views/ViewStart.cshtml:
#{
Layout = Request.IsAjaxRequest() ? null : "~/Views/Shared/_Layout.cshtml";
}
Since it is only a div I do not need a full html page with header and full body and stuff
You want a PartialView
You can return a View which has the Layout property value set to null
public class UserController : Controller
{
public ActionResult GetUserInfo()
{
return View();
}
}
and in GetUserInfo.cshtml
#{
Layout=null;
}
<h2>This is the UserInfo View :)</h2>
And you can call it from any page by using jQuery ajax methods
$("#someDivId").load("#Url.Action("User","GetUserInfo")");
If you want the Same Action method to handle an Ajax call and a Normal GET request call, ( Return the partial view on Ajax, Return normal view on Normal Http GET request), You can use the Request.IsAjax property to determine that.
public ActionResult GetUserInfo()
{
if (Request.IsAjaxRequest)
{
return View("Partial/GetUserInfo.cshtml");
}
return View(); //returns the normal view.
}
Assuming you have the Partial View (view with Layout set to null) is presetnt in Views/YourControllerName/Partial folder
How can I prevent the user from double clicking submit button on my signup form which is an ajax partial view?
I regret to ask since this would have already been asked. I just can't find a clear working answer now matter where I search. Disabling the button prevent submit. Using a var javascript clickcount+alert+return_false does not reset.
Environment: asp.net mvc3
View:
Form displays onload: #RenderPage("_SignupForm.cshtml")
Submission using:
#using (Ajax.BeginForm("Index", "Signup", null,
new AjaxOptions
{
UpdateTargetId = "signupForm",
InsertionMode = InsertionMode.Replace,
HttpMethod = "POST",
LoadingElementId="progress"
}
))
Submit control: <input type="submit" value="Sign up" />
SignupController :
public ActionResult Index()
{
return View();
}
[HttpPost]
public ActionResult Index(SignupModel formvalues)
{
Thread.Sleep(5000);
string errors = "";
if (TryValidateModel(formvalues))
{
errors = SignupAPI.Signup(formvalues); //includes custom validation
}
if (ModelState.IsValid == false || string.IsNullOrEmpty(errors) == false)
{
ViewBag.Errors = errors;
return PartialView("_SignupForm", formvalues);
}
else
return Redirect(string.Concat("http://localhost/welcome"));
}
Try with the following script:
$('form').submit(function () {
if ($(this).valid()) {
$(':submit', this).attr('disabled', 'disabled');
}
});
Make sure you execute it also in the success callback of your AJAX request in order to reattach the submit event when the form is replaced with a new content in the DOM, or the second time it might no longer work.
UPDATE: submission was not working because onclick was not returning true
<input type="submit" value="Sign Up" onclick="this.disabled = true; return true;"/>
this will disable the button and the second click on the button won't work