MVC4 WebGrid loaded from Ajax form - multiple calls to Controller when sorting and paging - ajax

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);
}
})
});

Related

Knockout.js AJAX Get

I am new to using Knockout.js and for that matter JavaScript as well. I went through their tutorials and tried to modify the example to load data from server as below. Can anyone please point what's wrong with my code
JavaScript:
jQuery(document).ready(function () {
MyViewModel = function()
{
var self =this;
self.name = ko.observable("");
self.getJson = function()
{
jQuery.ajax({
//Do all the work
success: function(data)
{
self.name = data.name;
}
});
}
}
myViewModelObj = new MyViewModel();
ko.applyBindings(myViewModelObj);
myViewModelObj.getJson();
});
View:
<h1 data-bind="text: name "></h1>
After you have declare an object to be an observable, it then becomes a native function to knockout. In order to update the value, use
self.name(data.name);
Otherwise you are overwriting the function.

on check box check send value to partial view and update it mvc 3

i have two partial view one is having a 5 checkbox (for filtering) and another will display the filtered data.
<input type="checkbox" data-toggle="checkbox" id="2000-5000"/>Rs.2000-Rs.5000
<input type="checkbox" data-toggle="checkbox" id="2000-5000"/>Rs.2000-Rs.5000
and with help of jquery am sending the request to controller..
public PartialViewResult PhonesPartail(int? id,string where)
{
var list = paginateRsult(id, "", where).ToList();
ViewData["totalpages"] = totalPages;
return PartialView("_phonelist",list);
}
and the jquery
$(document).on("change", ".price-checkbox input[type=checkbox]", function () {
if ($(this).is(":checked")) {
var prange = $(this).attr("id");
var parray = prange.split('-');
var whereclause = "price>=" + parray[0] + " and price <=" + parray[1];
$.ajax({
url: '../../Phones/PhonesPartail',
data: { where: whereclause },
type: 'POST',
success: function (data) {
$("#phone-list").append(data);
}
});
}
});
now the list which is returned , is getting added to the div with entire html page.. means again the html page is getting added in the div..
any solution..
Thanks in advance...
use html() function of jquery
It will paste data returned by partial view in to a div rather then appending it to div
$("#phone-list").html(data);

Calling multiple action methods (using ajax) and showing the result of last in a new tab

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.

Mixedup ajax response on mutliple Form.Request mootools

I have 2 Form.Request in 2 functions that are executed on 2 different buttons clicks
here is fiddle
http://jsfiddle.net/RtxXe/38/
seems like I did not set the events in right order in my functions since they are mixing up the responses. if you hit Clear cache and than Send you still get response from clear cache and vice versa. Unless you reload the page and click again you cant get the right response for each button as it should be .
Since this is not my original form and *I can only change it with js * , i added the clear cache button with new Element. I cant figure out as to why is this happening and any help is appreciated.
this is original html:
<div id="toolbar">
<ul>
<li id="adminsubmit">Send</li>
</ul>
</div>
<div id="response"></div>
<form action="http://www.scoobydoo.com/cgi-bin/scoobysnack" method="post" name="editform" id="myform">
<fieldset>
<!-- form elements go here -->
</fieldset>
<input type="hidden" name="task" value="">
</form>
​ and here is js:
var AdminForm = {
start: function() {
var toolbar = $$('#toolbar ul');
var addbtn2 = new Element('li', {
'id': 'cache',
'class': 'button',
html: 'Clear Cache'
});
addbtn2.inject(toolbar[0], 'top');
var btn1 = $('adminsubmit').getElement('a');
var btn2 = $('cache').getElement('a');
btn1.addEvent('click', function(event) {
event.preventDefault ? event.preventDefault() : event.returnValue = false;
AdminForm.formChange();
});
btn2.addEvent('click', function(event) {
event.preventDefault ? event.preventDefault() : event.returnValue = false;
AdminForm.clearCache();
});
},
formChange: function() {
var adminform = $('myform');
var target = $('response');
var adminsend = new Form.Request(adminform, target, {
onSend: function() {
target.set('html', 'formChange sending');
},
onComplete: function() {
target.set('html', 'formChange sent');
}
});
adminsend.send();
},
clearCache: function() {
var adminform = $('myform');
var target = $('response');
var clearingcahe = new Form.Request(adminform, target, {
onSend: function() {
target.set('html', 'clearCache sending');
},
onComplete: function() {
target.set('html', 'clearCache sent');
}
});
clearingcahe.send();
}
}
window.addEvent('domready', AdminForm.start);​
The Form.Request in Mootools inherits Class.Occlude, see http://mootools.net/docs/more/Class/Class.Occlude
But the Class.Occlude will prevent that several Objects are created and applied to the same DOM Element. That is, it works like a singleton, so the first time you do new Form.Request(adminform, ...) it will return a new instance of Form.Request.
However, the second time you call new Form.Request(adminform, ...) the previous object will be returned instead.
Your fiddle actually demonstrates this very good, because the first one that is clicked of "Clear Cache" or "Send" will be the one that initiates the object. The second time it will discard your options and just return the old object.
So there are two ways to solve this:
Create the Form.Request but don't set the event handlers through the options but through
adminsend.removeEvents('complete'); adminsend.addEvent('complete', ....)
Don't forget to remove the old event handlers before applying the new! otherwise you will just apply more and more eventhandlers.
There are two "buttons" so make two forms, which would be much more semantically correct as well.

Cascading DropDownList GridView ObjectDataSource

my page contains cascading DDL and grid view which working pure ajax.
the GridView taking data from sqlDataSouce which apply stored procedure that taking the DDL's values as parameters.
when i select value in the DDL the grid view changes it's data without refreshing the whole page.
yesterday i had to change the stored procedure and for some reason the sqlDataSource start having problems getting the data.
so after many attempts to make it work i finally try using ObjectDataSource, which managed to run the stored procedure properly and get the data. but now when i select value in the DDL's i get Page Error and the GridView stays as is.
who can i make it work ? (i didn't find a satisfying answer when i searched)
Thanks alot :-)
cascading Country and state DDL
#Html.DropDownListFor(model => model.CountryId, Model.CountryList, "--Select Country--", new { #class = "CountryList", style = "width:150px" })
#Html.DropDownListFor(model => model.StateId, Model.StateList, "--Select State--", new { #class = "StateList", style = "width:150px" })
<script type="text/javascript">
$(document).ready(function () {
$.post("/Client/GetModels", { id: $(".CountryList").val() }, function (data) {
populateDropdown($(".StateList"), data);
});
$(".CountryList").change(function () {
$.post("/Client/GetModels", { id: $(this).val() }, function (data) {
populateDropdown($(".StateList"), data);
});
});
});
function populateDropdown(select, data) {
$(".StateList").empty();
$.each(data, function (id, option) {
$(".StateList").append("<option value='" + option.StateId + "'>" + option.State + "</option>");
});
}
</script>

Resources