I have strange problem with an MVC3 application. The application works as expected on Firefox and Chrome but not IE.
I have a partial view that is repeated in a loop on a page. At the bottom of the partial view I am doing a check on Request.IsAuthenticated. If authenticated an ajax form should display. This works on all browsers but IE. In IE if there is more than one instance of the partial view rendered the form does not appear (even when the user is logged in).
I have attached the partial view code below. The form is right at the bottom.
Has anyone else had a similar problem? Any help or thoughts would be great. Thank you
#using HERE_MVC.Extenstions
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
#model HERE_MVC.Models.TilesModel
#{
Layout = String.Empty;
}
#foreach (var t in Model.NearestTiles)
{
<div id="div_tile #t.TileId" style="float: left; height: 235px; width: 300px; border: 0px solid black; margin: 5px;
background-color: #DDDDDD;">
<div id="tile #t.TileId" style="background-image: url(#Url.Action("GetTileBgImageWithCache", "Account", new { userName = #t.aspnet_Users.UserName })); background-repeat:no-repeat; height: 200px; width: 300px; border: 0px solid black;">
#t.aspnet_Users.UserName
<br />
#t.Title
<br />
#t.Quote1
<br />
#t.UpdatedDateTime
</div>
<div id="collectbtn #t.TileId" style="padding-top: 5px;">
#Html.ActionImage("Profile", "Home", new { userName = #t.aspnet_Users.UserName }, "~/content/img/Profile-Icon.jpg", "View Profile", "25")
#if (Request.IsAuthenticated)
{
if (#User.Identity.Name == #t.aspnet_Users.UserName)
{
}
else if (!Model.CheckAlreadyFollowing(#User.Identity.Name, #t.aspnet_Users.UserName))
{
#Ajax.ActionImageLink("../content/img/add.jpg", "Collect Tile", "25", "imgOn1" + #t.aspnet_Users.UserName, "AddFollowedUser", "Home", new { userToFollowName = #t.aspnet_Users.UserName }, new AjaxOptions { HttpMethod = "POST", OnSuccess = " $(#imgOn1" + #t.aspnet_Users.UserName + ").hide();" })
}
else
{
#Ajax.ActionImageLink("../content/img/minus.jpg", "Drop Tile", "25", "imgOff2" + #t.aspnet_Users.UserName, "UnFollow", "Home", new { userToUnFollowName = #t.aspnet_Users.UserName }, new AjaxOptions { HttpMethod = "POST" })
}
}
<a href="http://www.facebook.com/sharer.php?u=#t.ShareLink&t=%22+encodeURIComponent(document.title)" onclick="PopupCenter('http://www.facebook.com/sharer.php?u=#t.ShareLink&t=%22+encodeURIComponent(document.title)', 'NearRoar - Facebook',680,400); return false;" class="pin-it-button" count-layout="none">
<img src="../../Content/img/Facebook-share.jpg" width="25px" alt="Facebook share" /></a>
<a href="http://pinterest.com/pin/create/button/?url=#t.ShareLink&media=#t.PintrestShareImage&description=#t.PinterestShareText" onclick="PopupCenter('http://pinterest.com/pin/create/button/?url=#t.ShareLink&media=#t.PintrestShareImage&description=#t.PinterestShareText', 'NearRoar - Pin It',600,400); return false;" class="pin-it-button" count-layout="none" target="_blank">
<img border="0" src="../../Content/img/pintrest.jpg" title="Pin It" width="25px" /></a>
<a href="https://twitter.com/share" class="twitter-share-button" data-hashtags="#t.TwitterHashTag,NearRoar" data-url="#t.ShareLink" data-count="none" data-lang="en">
</a>
<script type="text/javascript" src="//platform.twitter.com/widgets.js"></script>
</div>
<div class="msg_list">
<p class="msg_head #t.TileId">
Comments (<span id='commentCount '#t.TileId>#t.CommentLines.Count</span>)</p>
<div class="msg_body">
#foreach (var c in t.CommentLines)
{
<div class="clearfix">
<div class="msg_body_comment_item_image">
<img width="50" src="#Url.Action("GetImageWithCache", "Home", new { userName = #c.aspnet_Users.UserName })"/>
</div>
<div class="msg_body_comment_item_text">
#c.CommentLine1
<br />
#c.CommentTime
</div>
</div>
}
<div id="newComment #t.TileId">
</div>
<div id="formdiv #t.TileId">
#if (Request.IsAuthenticated)
{
<div>
#using (Ajax.BeginForm("Comment" + #t.TileId, "Home", new { tileId = #t.TileId }, new AjaxOptions { }, new { id = "form" + #t.TileId, name = "form" + #t.TileId }))
{
<fieldset>
#Html.TextBox("commentText " + #t.TileId, null, new { #class = "msg_body_comment_text_input", maxlength = 200 })
<input type="hidden" name="date" id="cmTimeHidden #t.TileId" value="" />
<input type="hidden" name="commentText" id="commentTextHidden #t.TileId" value="" />
<input type="submit" value="Post Comment" onClick="setHiddens(#t.TileId);setNewComment(#t.TileId);"/>
</fieldset>
}
</div>
}
</div>
</div>
</div>
</div>
}
I would double check that rendered markup is valid. It's possible that one of your IDs is duplicated and that IE is just kicking it away.
UPDATE: You have 4 elements with ID t.titleId. As I said above, you can have only one specific ID on the HTML page. Move this to class instead and all should be fine.
Related
I would like to send my dynamically generated DIV items, but I do not understand how to do it
I create my div dynamically:
$("#clt_forf_ti").click(function(e){
e.preventDefault();
if(x < max_input){
x++;
var addDIV = $('<div id="clt_forf_div'+x+'" class="w-100 form-control" ><label for="clt_forf_num'+x+'">Ligne :</label> <input type="text" class="font-weight-bold border-top-0 border-right-0 border-left-0 mR-10" id="clt_forf_num'+x+'" autocomplete="off" style="width: 100px" > Forfait : <i id="clt_forf_forf_select'+x+'" style="display:none;"></i><input type="text" class="border-top-0 border-right-0 border-left-0" id="clt_forf_forf'+x+'" autocomplete="off" style="width: 180px"><i class="ti-calendar mL-20 mR-5"></i><input type="text" class="border-top-0 border-right-0 border-left-0" id="clt_forf_date_start'+x+'" name="datepicker" placeholder="Début" style="width: 80px"><i class="ti-calendar mL-20 mR-5"></i><input type="text" class="border-top-0 border-right-0 border-left-0" id="clt_forf_date_end'+x+'" name="datepicker" placeholder="Fin" style="width: 80px"><i id="clt_forf_ti_remove" class="ti-close c-red-500 mL-5" style="cursor: pointer;"></i></div>');
$('#clt_forf_bloc').append(addDIV);
$('#clt_forf_forf'+x, addDIV).autocomplete(autocomp_opt);
$("input[id^='clt_forf_date']", addDIV).datepicker(datepick);
//$('#clt_forf_date_end'+x, addDIV).datepicker(datepick);
}
});
I want to send each value by ajax:
var data = {
op_clt_forf_ref: $("[id^='clt_forf_forf_select']").html(),
op_clt_forf_num: $("[id^='clt_forf_num']").val(),
op_clt_forf_start: $("[id^='clt_forf_date_start']").val(),
op_clt_forf_end: $("[id^='clt_forf_date_end']").val(),
op_clt_forf_nom: $("[id^='clt_forf_forf']").val(),
};
however he only takes me the first div and not the others,
can you help me ?
The Elements that you have created were not present in the DOM earlier. So can't target them directly.
Try doing this:
var data = {
op_clt_forf_ref: $('body #clt_forf_forf_select').html(),
op_clt_forf_num: $('body #clt_forf_num').val(),
op_clt_forf_start: $('body #clt_forf_date_start').val(),
op_clt_forf_end: $('body #clt_forf_date_end').val(),
op_clt_forf_nom: $('body #clt_forf_forf').val(),
};
As I know, when popup/modal opens it is on the top of page where I opened it from, so when try to fill value in it don't see where it need to do that.
Then tried to look inside modal with within. Tried other classes, but same Capybara::ElementNotFound: Unable to find visible css "modal183"
within('modal183') do
fill_in 'ctl00_ContentPlaceHolder1_tbId', :with => '10'
end
Tried also find with xpath, but also nothing
Capybara::ElementNotFound: Unable to find visible xpath "//input[#id='ctl00_ContentPlaceHolder1_tbId']"
<div class="modal183">
<div class="popup_Titlebar" id="PopupHeader">
<div class="TitlebarLeft">
<span id="ctl00_ContentPlaceHolder1_lblTitle">text</span>
</div>
</div>
<div class="popup_Body">
<div class="popup_TextNoTop">
<span id="ctl00_ContentPlaceHolder1_lbl1" class="label-left">text</span>
<input name="ctl00$ContentPlaceHolder1$tbId" type="text" id="ctl00_ContentPlaceHolder1_tbId" class="txtInputDec width100" onFocus="ClearTheTextbox(this, '')" onBlur="FillTheTextbox(this, '')" onkeypress="return onlyNumbers()" />
<span id="ctl00_ContentPlaceHolder1_cmrvIB" style="color:Red;display:none;"><a href='#' class='tooltipCons'><img src='/App_Themes/Default/img/exclamation.png' alt='' /><span>text</span></a></span>
<span id="ctl00_ContentPlaceHolder1_revIB" style="color:Red;display:none;"><a href='#' class='tooltipCons'><img src='/App_Themes/Default/img/exclamation.png' alt='' /><span>text
</span></a></span>
<span id="ctl00_ContentPlaceHolder1_rvTb" style="color:Red;display:none;"><a href='#' class='tooltipCons'><img src='/App_Themes/Default/img/exclamation.png' alt='' /><span>text</span></a></span>
<br />
<span id="ctl00_ContentPlaceHolder1_lbl2" class="label-left">text</span>
<input name="ctl00$ContentPlaceHolder1$tb1Id" type="text" id="ctl00_ContentPlaceHolder1_tb1Id" class="txtInputDec width100 top5" onFocus="ClearTheTextbox(this, '')" onBlur="FillTheTextbox(this, '')" onkeypress="return onlyNumbers()" />
<span id="ctl00_ContentPlaceHolder1_rev1IB" style="color:Red;display:none;"><a href='#' class='tooltipCons'><img src='/App_Themes/Default/img/exclamation.png' alt='' /><span>text
</span></a></span>
<span id="ctl00_ContentPlaceHolder1_rvTb1" style="color:Red;display:none;"><a href='#' class='tooltipCons'><img src='/App_Themes/Default/img/exclamation.png' alt='' /><span>text</span></a></span>
</div>
</div>
<div class="popup_Buttons">
<table width="100%" border="0" cellpadding="0" cellspacing="0">
<tr>
<td align="left">
<a onclick="if (!window.event) {this.disabled=true; this.style.color='grey'; var but = document.getElementById('btnCancel'); but.disabled=true; but.style.color='grey';};" id="ctl00_ContentPlaceHolder1_btnOkay" class="cool-button width80 blue" href="javascript:WebForm_DoPostBackWithOptions(new WebForm_PostBackOptions("ctl00$ContentPlaceHolder1$btnOkay", "", true, "cons", "", false, true))">text</a>
</td>
<td align="right">
<a id="ctl00_ContentPlaceHolder1_btnCancel" class="cool-button width80 black" href="javascript:__doPostBack('ctl00$ContentPlaceHolder1$btnCancel','')">Atcelt</a>
</td>
</tr>
</table>
</div>
</div>
Updated: Attached part where it looks like be this popup which appear after I press on button. Inspect code shows on this code. I didn't wrote this code, but trying to build automated tests on them :)
<div id="popupparent" style="display: block; height: 1e+06px; left: 0px; top: 0px;">
<script language="javascript" type="text/javascript">
function resizeIframe(obj) {
var h = "innerHeight" in window ? window.innerHeight : document.documentElement.offsetHeight;
var w = "innerWidth" in window ? window.innerWidth : document.documentElement.offsetWidth;
var doc;
if (obj.contentDocument) {
doc = obj.contentDocument;
} else if (obj.contentWindow) {
doc = obj.contentWindow.document;
} else {
return;
}
var childHeight = doc.body.scrollHeight;
var childWidth = doc.body.scrollWidth;
obj.style.height = childHeight + 'px';
var ver = getInternetExplorerVersion();
var parent = document.getElementById('termsofuse');
if (ver > -1) {
parent.style.marginTop = ((h - childHeight) / 2) - 64 + 'px';
} else {
parent.style.top = ((h - childHeight) / 2) - 64 + 'px';
}
parent.style.left = ((w - childWidth) / 2) + 'px';
parent.style.display = "block";
parent.style.visibility = "visible";
}
<div id="termsofuse" style="left: 1081px; top: 246.5px; display: block; visibility: visible;">
<iframe id="frameeditexpanse" frameborder="0" src="PopDailyCons.aspx?e=c0lEPUMwMDEwMDA3MDQmYUlEPUEwMDQ3ODIxNzAmZGF5PTImY3VyRGF0ZT0yMDE4LjA0JmN1clVzZXI9MTYwJnNvdXJjZT1hdGs=" scrolling="no" marginheight="0" marginwidth="0" class="termsOfUseFrame" style="height: 185px;"> </iframe>
<div class="popup_Buttons" style="display: none">
<input id="btnOk" value="Done" type="button">
<input id="btnCancel" value="Cancel" type="button" onclick="hideusertermwindow()">
Your within call doesn't work because you need to pass it a CSS selector. You're passing modal183 which would look for a <modal183> element, when what you really want is .modal183 to look for an element with the class modal183.
From your description it's tough to tell exactly what you're doing but it sounds like maybe you're talking about a popup window (new browser window) opened on top of the current tab. If that is the case then you'd need to swap windows in order to interact with it, along the lines of
new_win = window_opened_by do
# whatever action in the original window causes the popup window to open
click_button 'blah'
end
within_window(new_win) do
fill_in 'ctl00_ContentPlaceHolder1_tbId', :with => '10'
end
I've looked over the the times this question has been asked, and I've implemented any errors they had. Nevertheless, I'm still getting two processes running when I call an Ajax Actionlink and there's a bootstrap modal involved. Here are all the relevant codes. I'm including the Bundle to show only one of each of the unobtrusives are included:
Layout:
<div id="mainBody" class="container body-content">
#RenderBody()
</div>
#Scripts.Render("~/bundles/jquery")
#Scripts.Render("~/bundles/jqueryval")
#Scripts.Render("~/bundles/bootstrap")
#RenderSection("scripts", required: false)
Controller:
public ActionResult ReleaseVersion(string LOCATION_NUMBER = "")
{
...
model.ReleaseVersionVMInfo = rvmodel;
if (Request.IsAjaxRequest())
{
return PartialView("_ReleaseVersion", model);
}
return PartialView("_ReleaseVersion", model);
}
View:
<div class="col-xs-3">
<div class="row Padding_ExtraVert">
#Ajax.ActionLink("Release Version",
"ReleaseVersion",
"Stores",
new { LOCATION_NUMBER = Model.storeNbr },
new AjaxOptions
{
UpdateTargetId = "ReleaseModal",
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET",
OnFailure = "storeSearchFailed"
},
new
{
#class = "col-xs-5 btn btn-sm button-v
button-alt-1 Button_StoreHead",
#id = "BtnViewRelease",
data_target = "#ReleaseModal",
data_toggle = "modal"
})
</div>
View being called by this Actionlink:
#model EpmPortal.Models.App.StoreReleaseVersionVM
#if (Model != null && Model.storeNbr != "0")
{
....
Close
Thank you for your help
Oh brother, it turns out it's something TOTALLY different than I expected. In another branch of my program, one I hadn't really started working on yet, I had a partial view in there and it had _Layout from the shared folder on the top - and nothing else but a title in the razor to identify it. That's what caused the duplicate run. I have no idea why. That code is never touched. Sorry about this.
When I run the following and put a breakpoint in the action ReleaseVersion, it only hits this action once, so there is only one process.
Controller/Model:
public class StoreViewModel
{
public int storeNbr { get; set; }
}
public class HomeController : Controller
{
public PartialViewResult ReleaseVersion(string LOCATION_NUMBER = "")
{
return PartialView("_ReleaseVersion"); //, model
}
public ActionResult IndexValid4()
{
var storeViewModel = new StoreViewModel { storeNbr = 5 };
return View(storeViewModel);
}
Partial view _ReleaseVersion in share folder:
release version partial view
View:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>IndexValid4</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
#*MAKE SURE to put the next script in*#
<script src="http://ajax.aspnetcdn.com/ajax/mvc/3.0/jquery.unobtrusive-ajax.min.js"></script>
<script type="text/javascript">
$(function () {
$('#LOCATION_NUMBER').click(function () {
var store = $('#storeNbr').val();
this.href = this.href.split("?")[0];
this.href = this.href + '?LOCATION_NUMBER=' + encodeURIComponent(store);
});
})
</script>
</head>
<body>
<div>
<a data-toggle="modal" data-target="#myModal">
Click to Open modal
</a>
</div>
<div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="myModalLabel">My Modal</h4>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<label class="btnIEFix">You can also, Click in Grey Area Outside Modal To Close</label>
<button title="You can also, Click in Grey Area Outside Modal To Close" type="button" class="btn btn-secondary bootBtnMargin" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
#Html.HiddenFor(r => r.storeNbr, new { id = "storeNbr" })
#*https://stackoverflow.com/questions/5838273/actionlink-routevalue-from-a-textbox*#
#Ajax.ActionLink(
"Trigger Ajax",
"ReleaseVersion",
null,
new AjaxOptions
{
UpdateTargetId = "result",
InsertionMode = InsertionMode.Replace,
HttpMethod = "GET"
},
new
{
id = "LOCATION_NUMBER",
#class = "col-xs-5 btn btn-sm button-vbutton-alt-1 Button_StoreHead",
data_target = "#myModal",
data_toggle = "modal"
}),
<div id="result"></div>
</body>
</html>
I am trying to use a template as shown below, the outcome is a view with all elements from the template on one line, even though i am using to separate the elements. Why does this not display properly? It seems that no matter what styling i do it still ends up a single line view.
UPDATE
The culprit is the kendo style sheet - kendo.mobile.all.min.css -
So the new question for a kendo expert is why does kendo handle input fields differently when they appear in a listview via a template than when they appear outside of a template?
An input field outside of a listview template gets this class
.km-ios .km-list input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]):not([type="checkbox"]):not([type="radio"]):not(.k-input):not(.k-button), .km-ios .km-list select:not([multiple]), .km-ios .km-list .k-dropdown-wrap, .km-ios .km-list textarea
Which results in no odd styling rules :) Normal text field view
An input field inside of the template gets this class
.km-root input:not([type="button"]):not([type="submit"]):not([type="reset"]):not([type="image"]):not([type="checkbox"]):not([type="radio"]):not(.k-input):not(.k-button), .km-root select:not([multiple]), .km-root .k-dropdown, .km-root textarea
which results in these rules being applied to it (making the field sit in a wierd spot and loose all normal field stlying ie border background etc.) Im not 100% sure which wrapper is causing this
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
font-size: 1.1rem;
color: #385487;
min-width: 6em;
border: 0;
padding: .4em;
outline: 0;
background:
transparent;
My work around is to give any text fields inside listview templates the class="k-input" which obviously excludes them from the above css -
<script src="kendo/js/jquery.min.js"></script>
<script src="kendo/js/kendo.mobile.min.js"></script>
<link href="kendo/styles/kendo.mobile.all.min.css" rel="stylesheet" />
<!-- eventDetail view -------------------------------------------------------------------------------------------------->
<div data-role="view" id="view-eventDetail" data-show="getEventDetailData" data-title="eventDetail">
<header data-role="header">
<div data-role="navbar">
<span data-role="view-title"></span>
<a data-align="right" data-role="button" class="nav-button" href="#view-myEvents">Back</a>
</div>
</header>
<form id="updateEventForm">
<div id="updateEvent">
<div id="eventDetail"></div>
<p>
<input type="button" id="eventUpdateCancelButton" style="width:30%" data-role="button" data-min="true" value="Back" />
<input type="submit" id="eventUpdateSaveButton" style="width:30%" data-role="button" data-min="true" value="Save" />
</p>
<div id="eventResult"></div>
</div>
</form>
</div>
<script id="eventDetail-template" type="text/x-kendo-template">
<p>
<input name="event_type" id="event_type" data-min="true" type="text" value="#= type #" />
</p>
<p>
<input name="event_loc" id="event_loc" data-min="true" type="text" value="#= type #" />
</p>
<p>
<input name="event_date_time" id="event_date_time" data-min="true" type="datetime" value="#= stamp #" />
</p>
<p>
Share this
<input data-role="switch" id="event_share" data-min="true" checked="checked" value="#= share #"/>
</p>
<input name="userID" id="userID" type="hidden" value="#= user_id #" />
<input name="eventID" id="eventID" type="hidden" value="#= event_id #" />
</script>
<script>
function getEventDetailData(e) {
var dataSource = new kendo.data.DataSource({
transport: {
read: {
url: "http://localhost/mpt/website/api/event_details.php",
dataType: "jsonp",
type: "GET",
data: { userID: e.view.params.user_id, eventID: e.view.params.event_id },
cache: false
},
parameterMap: function(options) {
return {
userID: options.userID,
eventID: options.eventID
};
}
},
schema: { // describe the result format
data: "results" // the data which the data source will be bound to is in the "results" field
}
});
console.log(e);
$("#eventDetail").kendoMobileListView({
dataSource: dataSource,
template: kendo.template($("#eventDetail-template").html())
}).data("kendoMobileListView");
}
//update event
function sendUpdateEvent() {
var siteURI = "http://localhost/mpt/website/api/update_event.php?";
app.showLoading();
var user_id = $('#userID').val();
var event_id = $('#eventID').val();
var event_type = $('#event_type').val();
var event_loc = $('#event_loc').val();
var event_date_time = $('#event_date_time').val();
var event_share = $('#event_share').val();
var formVals = 'eventID=' + event_id + '&userID=' + user_id + '&event_type=' + event_type + '&event_loc=' + event_loc + '&event_date_time=' + event_date_time + '&event_share=' + event_share;
var fullURI = siteURI + formVals;
$.ajax({
url: fullURI, dataType: 'json', success: function (data) {
$('#eventResult').html(data.results);
app.hideLoading();
app.navigate("#view-myEvents");
}
});
}
$('#eventUpdateCancelButton').click(function () {
app.navigate("#view-myEvents");
});
$('#eventUpdateSaveButton').click(function () {
sendUpdateEvent();
});
$('#updateEventForm').submit(function () {
sendUpdateEvent();
return false;
});
</script>
ListView widgets are supposed to be applied to <ul> elements.
Try changing:
<div id="eventDetail"></div>
to:
<ul id="eventDetail"></ul>
Also with this bit of code:
$("#eventDetail").kendoMobileListView({
dataSource: dataSource,
template: kendo.template($("#eventDetail-template").html())
}).data("kendoMobileListView");
The .data() call on the end isn't doing anything here and can be removed, and also you can pass just the text string as the template. You don't need to call kendo.template() yourself. So you can change that to just:
$("#eventDetail").kendoMobileListView({
dataSource: dataSource,
template: $("#eventDetail-template").html()
});
I am having a similar as the user here:
ASP.NET MVC 3.0 WebGrid - Ajax Enabled
and here:
http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/22be0501-1b0b-496a-9d0c-f9f5138996ac
However none of the suggestions from those threads is helping here.
The specifics: When running in my local debugger on my Windows 7/IIS 8 machine, I am getting the 'A jQuery script reference is required in order to enable Ajax support in the "WebGrid" helper' message after an ajax postback trying to update. The same code however does not result in a problem in FF or when using IE against a version of the same code running on a server (IIS 6). To make it more confusing, when I first wrote this code, I never had this problem - it has only recently started throwing error.
When the error occurs, the 'correct' updated content flashes on the screen before the whole page redirects to just the partial view and throws the popup on screen. It's like it works correctly but something then resets it and causes it to stop using ajax.
To try to troubleshoot I replaced the .min versions of jquery with the full versions and step through the code. In the debugger the jQuery behaves differently and triggers an "ajaxStop" because of the value in the global ajax counter. When I run the external script debugger against the server version of the same code in the same IE browser, this does not appear to occur (though I still have the 'min' version on the server so can't see exactly). It is this ajaxStop which appears to be forcing the webgrid to think that jQuery isn't loaded on the page and doesn't let the ajax behavior 'stick' - forcing a new page load of just the partial view.
My page is not ultra simple: the main View has several divs containing partial views - divSearch contains the search partial view which is the one calling the ajax postback, and divList contains the grid that needs to be updated based on the search results. The controller method called by the search partial view renders the 'new' contents for the search results into a string and sends them back as part of a JsonResult, which is then assigned to the divList's html via jQuery. (I use this technique in several places in the app, and I've verified that the string being sent back is the correct content for the partial view).
The jQuery (and all the other js includes) are at the top of the layout page and render before any of the partial views.
Main View:
#model WebUI.Areas.Admin.Models.Client.ManageClientsModel
#{
ViewBag.Title = "ManageClients";
Layout = "~/Views/Shared/_AdminLayout.cshtml";
}
<style type="text/css">
.inv
{
visibility: hidden;
}
.vis
{
visibility: visible;
}
</style>
<h2>ManageClients</h2>
<div id="divSearch">
#Html.Partial("_ClientSearch", Model.ClientList)
</div>
<div id="divList">
#Html.Partial("_ClientList", Model.ClientList)
</div>
<br/>
<div id="newSection">
<div id="divEdit"> <button class="toggleNew">
Add Client
</button>
</div>
div class="inv" id="divNew"> #Html.Partial("_Create", Model.ClientVM ) </div>
</div>
<script type="text/javascript">
$(function () {
$(".toggleNew").click(function () {
$("#divNew").toggleClass("inv");
return false;
});
});
</script>
Client Search partial:
#using Core.Entities
#model WebUI.Areas.Admin.Models.Client.ClientListViewModel
<div id="clientSearch">
<form action="#Url.Action("JsonSearch","Client", new { area = "Admin"})"
onsubmit="return formSearchSubmit(this);" >
<div class="divReturnErrors"></div>
#if(TempData["SearchName"] == null)
{
TempData["SearchName"] = Model.SearchName;
}
<br/>
#Html.LabelFor(model => model.SearchName, "Search for client name:")
#Html.EditorFor(model => model.SearchName)
<input type="submit" value="Search" name="btnSearch" />
</form>
</div>
<script type="text/javascript">
function formSearchSubmit(formToSubmit) {
//this line submits the form data to the JsonSearch method
$.post($(formToSubmit).attr("action"),
{ SearchName: $(formToSubmit).find("#SearchName").val() },
function (data) { //this is the callback from JsonSearch
// that handles the results.
if (data["IsSuccess"] == undefined || data["IsSuccess"] == false)
$('div.divReturnErrors').html(data["Errors"]);
//put errors in divReturnErrors
else { //if it succeeds we stuff the string containing the
//partial view into the _ClientList control's div to
//replace previous grid.
$("#divList").html(data["PartialViewHtml"]);
}
}, "json");
return false;
}
</script>
Client List partial view
#model WebUI.Areas.Admin.Models.Client.ClientListViewModel
<h2>Clients</h2>
#helper TrueFalseToYesNo(bool val) {
if (val)
{
<span>Yes</span>
}
else
{
<span>No</span>
}
}
<div id="clientListing">
#{
if(Model.SearchName != null)
{
TempData["SearchName"] = Model.SearchName;
}
var grid = new WebGrid<NeedleFinder.Core.Entities.Client>(null, rowsPerPage: 10,
canSort: false, defaultSort: "ClientName",
ajaxUpdateContainerId:"clientListing");
grid.Bind(Model.Clients, rowCount: Model.TotalRows, autoSortAndPage: false);
#grid.GetHtml(htmlAttributes: new {id= "clientListing"},
tableStyle: "table90",
alternatingRowStyle: "duncanTableAltRows",
headerStyle: "duncanTableHeaders",
columns: grid.Columns(
grid.Column(columnName: "Client ID", style: "logcolumn",
format: item =>
#Ajax.ActionLink(((object)item.ClientID).ToString(),
Model.LinkAction, new { id = item.ClientID }, new AjaxOptions {
HttpMethod = "GET", UpdateTargetId = "divEdit", InsertionMode =
InsertionMode.Replace })),
grid.Column(columnName: "ClientName", style: "logcolumn"),
grid.Column(columnName: "Abbreviation", style: "logcolumn"),
//grid.Column(columnName: "IsActive", style: "logcolumn")
grid.Column(columnName: "IsActive", style: "logcolumn",
format: item => #TrueFalseToYesNo(item.IsActive))
)
)
}
</div>
The 'error' is occurring in this line:
$("#divList").html(data["PartialViewHtml"]);
from the Client Search partial view ajax callback function. This line jumps into the jQuery and while in there is calling ajaxStop.
Please note I have already incorporated the suggestions from the other thread (jQuery includes at the top, putting the webgrid in a div with the same name as the ajaxUpdateContainer target, adding an id property to the webgrid with the same name as well. The suggestion to load the partial explicitly in document.ready does not seem applicable to me since I am trying to populate this div with a string based on inputs rather than just plain load the default 'get' version of the partial view.
Here is the rendered html for the page when it first loads (it doesn't update when you do the ajax post anyway): (Apologies for the wonky formatting but adding 4 indents to each line didn't really work cleanly)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>ManageClients</title>
<link href="/Content/Site.css" rel="stylesheet" type="text/css" />
<link href="/Content/themes/redmond/jquery-ui.css" rel="stylesheet" type="text/css" />
<script src="/Scripts/jquery-1.5.1.js" type="text/javascript"></script>
<script src="/Scripts/jquery-ui-1.8.18.js" type="text/javascript"></script>
<script src="/Scripts/jquery.unobtrusive-ajax.js" type="text/javascript"></script>
<script src="/Scripts/modernizr-1.7.js" type="text/javascript"></script>
<script src="/Scripts/jQuery.Validate.js" type="text/javascript"></script>
<script src="/Scripts/jquery.validate.unobtrusive.js" type="text/javascript"></script>
</head>
<script type="text/javascript">
$(document).ready(function () {
$('.dateEditor').datepicker({ dateFormat: "mm/dd/yy" });
});
//This function should be called in your ajax post return are staying on same page
after success and/or you do have server side errors that couldn't have been found
//if there were still client side errors, otherwise you will still have the previous
validation errors.
function ClearValidationErrors() {
var container = $('form').find('[data-valmsg-summary="true"]');
var list = container.find('ul');
if (list && list.length) {
list.empty();
container.addClass('validation-summary-valid').removeClass('validation-summary-
errors');
}
}
</script>
<body>
<style type="text/css">
#topbar
{
background-color: Black;
margin: 0px;
padding: 5px;
}
#adminmenu_nav a
{
float: left;
width: 150px;
}
.logcolumn
{
padding-left: 10px;
padding-right: 10px;
border: 1px solid #BBBBBB;
}
</style>
<script>
$(function () {
$("#adminmenu_nav a").button();
$("#optionsPanel input").button();
$(".asButton input").button();
});
</script>
<div id="topbar">
<img style="margin-left:20px" src="/Content/Images/Logos/NFWhite.png"
alt="NeedleFinder" />
</div>
<div>
<div id="adminmenu_nav" style="float: left">
<table>
<tr><td>Clients</td></tr>
<tr><td>Cases</td></tr>
<tr><td>Users</td></tr>
<tr><td>Roles</td></tr>
<tr><td>Logs</td></tr>
</table>
</div>
<div style="overflow: hidden">
<span class="validation-summary-errors"></span>
<style type="text/css">
.inv
{
visibility: hidden;
}
.vis
{
visibility: visible;
}
</style>
<h2>ManageClients</h2>
<div id="divSearch">
<div id="clientSearch">
<form action="/Admin/Client/JsonSearch" onsubmit="return formSearchSubmit(this);" >
<div class="divReturnErrors"></div>
<br/>
<label for="SearchName">Search for client name:</label>
<input class="text-box single-line" id="SearchName" name="SearchName"
type="text" value="" />
<input type="submit" value="Search" name="btnSearch" />
</form>
</div>
<script type="text/javascript">
function formSearchSubmit(formToSubmit) {
//this line submits the form data to the JsonSearch method
$.post($(formToSubmit).attr("action"),
{ SearchName: $(formToSubmit).find("#SearchName").val() },
function (data) {
if (data["IsSuccess"] == undefined || data["IsSuccess"] == false)
$('div.divReturnErrors').html(data["Errors"]);
else
{
$("#divList").html(data["PartialViewHtml"]);
}
}, "json");
return false;
}
</script>
</div>
<div id="divList">
<h2>Clients</h2>
<div id="clientListing">
<script type="text/javascript">if (typeof(jQuery)=='undefined') alert("A jQuery script
reference is required in order to enable Ajax support in the \"WebGrid\" helper.");
</script><table class="table90" id="clientListing"><thead><tr
class="duncanTableHeaders"><th scope="col">Client ID</th><th
scope="col">ClientName</th><th scope="col">Abbreviation</th><th
scope="col">IsActive</th></tr></thead><tfoot><tr><td colspan="4">1 <a href="#"
onclick="$('#clientListing').load('/Admin/Client/ManageClients?page=2&
amp;__=634750282355486955 #clientListing');">2</a> <a href="#"
onclick="$('#clientListing').load('/Admin/Client/ManageClients?page=3&
amp;__=634750282355506956 #clientListing');">3</a> <a href="#"
onclick="$('#clientListing').load('/Admin/Client/ManageClients?page=4&
amp;__=634750282355516957 #clientListing');">4</a> <a href="#"
onclick="$('#clientListing').load('/Admin/Client/ManageClients?page=5&
amp;__=634750282355536958 #clientListing');">5</a> <a href="#"
onclick="$('#clientListing').load('/Admin/Client/ManageClients?page=2&
amp;__=634750282355546959 #clientListing');">></a> </td></tr></tfoot><tbody>
<tr><td class="logcolumn"><a data-ajax="true" data-ajax-method="GET" data-ajax-
mode="replace" data-ajax-update="#divEdit" href="/Admin/Client/_Edit/2042">2042</a>
</td><td class="logcolumn">ABC Legal</td><td class="logcolumn">ABC123</td><td
class="logcolumn"> <span>Yes</span>
</td></tr><tr class="duncanTableAltRows"><td class="logcolumn"><a data-ajax="true"
data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#divEdit"
href="/Admin/Client/_Edit/2044">2044</a></td><td class="logcolumn">ABC Legal
3</td><td class="logcolumn">ABC125</td><td class="logcolumn">
<span>Yes</span>
</td></tr><tr><td class="logcolumn"><a data-ajax="true" data-ajax-method="GET"
data-ajax-mode="replace" data-ajax-update="#divEdit" href="/Admin/Client/_Edit
/2045">2045</a></td><td class="logcolumn">ABC Legal 4b</td><td
class="logcolumn">ABC4bb</td><td class="logcolumn"> <span>Yes</span>
</td></tr><tr class="duncanTableAltRows"><td class="logcolumn"><a data-ajax="true"
data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#divEdit"
href="/Admin/Client/_Edit/2047">2047</a></td><td class="logcolumn">ABC Legal
6</td><td class="logcolumn">ABC6</td><td class="logcolumn"> <span>No</span>
</td></tr><tr><td class="logcolumn"><a data-ajax="true" data-ajax-method="GET"
data-ajax-mode="replace" data-ajax-update="#divEdit" href="/Admin/Client/_Edit
/2048">2048</a></td><td class="logcolumn">ABC Legal 7</td><td
class="logcolumn">ABC7</td><td class="logcolumn"> <span>Yes</span>
</td></tr><tr class="duncanTableAltRows"><td class="logcolumn"><a data-ajax="true"
data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#divEdit"
href="/Admin/Client/_Edit/2049">2049</a></td><td class="logcolumn">ABC Legal
8a</td><td class="logcolumn">ABC8a</td><td class="logcolumn">
<span>Yes</span>
</td></tr><tr><td class="logcolumn"><a data-ajax="true" data-ajax-method="GET"
data-ajax-mode="replace" data-ajax-update="#divEdit" href="/Admin/Client/_Edit
/2050">2050</a></td><td class="logcolumn">ABC Legal 9</td><td
class="logcolumn">ABC9</td><td class="logcolumn"> <span>Yes</span>
</td></tr><tr class="duncanTableAltRows"><td class="logcolumn"><a data-ajax="true"
data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#divEdit"
href="/Admin/Client/_Edit/2043">2043</a></td><td class="logcolumn">ABC
Legal2</td><td class="logcolumn">ABC124</td><td class="logcolumn">
<span>Yes</span>
</td></tr><tr><td class="logcolumn"><a data-ajax="true" data-ajax-method="GET"
data-ajax-mode="replace" data-ajax-update="#divEdit" href="/Admin/Client/_Edit
/2046">2046</a></td><td class="logcolumn">ABC Legal5</td><td
class="logcolumn">ABC5</td><td class="logcolumn"> <span>Yes</span>
</td></tr><tr class="duncanTableAltRows"><td class="logcolumn"><a data-ajax="true"
data-ajax-method="GET" data-ajax-mode="replace" data-ajax-update="#divEdit"
href="/Admin/Client/_Edit/2051">2051</a></td><td class="logcolumn">ACE Legal
1</td><td class="logcolumn">ACE1</td><td class="logcolumn">
<span>Yes</span>
</td></tr></tbody></table></div>
</div>
<br/>
<div id="newSection">
<div id="divEdit"> <button class="toggleNew">
Add Client
</button></div><!-- placeholder for _Edit., will replace button when editing//-->
<div class="inv" id="divNew">
<form action="/Admin/Client/JsonCreate" onsubmit="return formCreateSubmit(this);">
<div>
<div class="titleText">Add Client</div>
<div class="divClientSideVal">
<div class="validation-summary-valid" data-valmsg-summary="true"><ul><li
style="display:none"></li>
</ul></div>
</div>
<div class="divCreateErrors"></div>
<fieldset>
<table>
<tr>
<td>
<label for="CurrentClient_ClientName">Name</label>
</td>
<td><input data-val="true" data-val-length="Client Name must be 50
characters or fewer" data-val-length-max="50" data-val-
required="Client Name is required."
id="CurrentClient_ClientName" name="CurrentClient.ClientName"
style="width: 120px;" type="text" value="" />
</td>
<td><span class="field-validation-valid" data-valmsg-
for="CurrentClient.ClientName" data-valmsg-replace="false">*</span></td>
</tr>
<tr>
<td>
<label for="CurrentClient_Abbreviation">Abbreviation</label>
</td>
<td><input data-val="true" data-val-length="Abbreviation must be 6
characters or fewer" data-val-length-max="6" data-val-
required="Abbreviation is required." id="CurrentClient_Abbreviation"
name="CurrentClient.Abbreviation" style="width: 120px;" type="text"
value="" />
</td>
<td><span class="field-validation-valid" data-valmsg-
for="CurrentClient.ClientName" data-valmsg-replace="false">*</span></td>
</tr>
<tr>
<td>
<label for="CurrentClient_IsActive">Is Active:</label>
</td>
<td>
<input data-val="true" data-val-required="The IsActive field is
required." id="CurrentClient_IsActive" name="CurrentClient.IsActive"
type="checkbox" value="true" /><input name="CurrentClient.IsActive"
type="hidden" value="false" />
</td>
</tr>
<tr>
<td>Enable for installations:</td>
<td>
<select Multiple="multiple" id="SelectedInstallations"
multiple="multiple" name="SelectedInstallations" style="width:
120px;">
<option value="1">Dev</option>
<option value="2">FakeDev</option>
</select>
</td>
</tr>
</table></fieldset>
<p>
<input type="submit" value="Create" name="btnCreate" />
</p>
</div>
</form>
<script type="text/javascript">
function formCreateSubmit(formToSubmit) {
var installations = "";
$('#SelectedInstallations option').each(function (i) {
if (this.selected == true) {
if (installations.length > 0) {
installations += ",";
}
installations += this.value;
}
});
//this line submits the form data to the JsonCreate method
$.post($(formToSubmit).attr("action"),
{ ClientName: $(formToSubmit).find("#CurrentClient_ClientName").val(),
Abbreviation:
$(formToSubmit).find("#CurrentClient_Abbreviation").val(),
IsActive:
$(formToSubmit).find("#CurrentClient_IsActive").is(':checked'),
SelectedInstallations: installations,
Action: "Create"
},
function (data) {
if (data["IsSuccess"] == undefined || data["IsSuccess"] == false)
{
if (data["Errors"] != undefined &&
data["Errors"].length > 0)
{
ClearValidationErrors();
('div.divCreateErrors').html(data["Errors"]);
} else {
$('div.divCreateErrors').html("");
}
} else {
window.location = "/Admin/Client/ManageClients";
}
}, "json");
return false;
}
</script> </div><!-- placeholder for _Create //-->
</div>
<script type="text/javascript">
$(function () {
$(".toggleNew").click(function () {
$("#divNew").toggleClass("inv");
return false;
});
});
</script>
</div>
</div>
<div class="InternalMenu">Logging - <a href="/Admin
/Client/ManageClients">Client</a></div>
</body>
</html>
Any suggestions would be greatly appreciated as I'm pretty stumped as to what is causing the problem.
Ok - I figured out the problem, so figure I will report back.
The issue was actually more MVC than jQuery related but it was a problem with how I had put my search form together.
I had my search button set with type=submit and also had an onsubmit eventhandler doing the ajax postback. What was occurring was that the ajax postback was doing exactly what it was supposed to but when it completed, the submit was triggering a "get" and the "get" handler in the controller for the search form returns just a PartialViewResult --> so this was replacing the screen contents as a normal load. The error was being thrown because the partial view did not have the layout with the includes.
So the fix was to
a) change the button type to "button"
b) remove the "onsubmit" from the form declaration
c) add an id="frmSearch" to the form (for easy selectivity)
d) modify the formSearchSubmit to take the action from $("#frmSearch").action instead of from the function argument (which is a button, not the form).
After that, it submitted the ajax post correctly and handled the return update w/o triggering another submit of the form.