I've got a particularly large form in an page. When the form is validated and a field is invalid, I want to scroll the window to that control. Calling the control's Focus() doesn't seem to do this. I've found a JavaScript workaround to scroll the window to the control, but is there anything built into ASP.NET?
Page.MaintainScrollPositionOnPostBack = False
Page.SetFocus(txtManagerName)
Are you using a Validation Summary on your page?
If so, ASP.NET renders some javascript to automatically scroll to the top of the page which may well override the automatic behaviour of the client side validation to focus the last invalid control.
Also, have you turned client side validation off?
If you take a look at the javascript generated by the client side validation you should see methods like this:
function ValidatorValidate(val, validationGroup, event) {
val.isvalid = true;
if ((typeof(val.enabled) == "undefined" || val.enabled != false) &&
IsValidationGroupMatch(val, validationGroup)) {
if (typeof(val.evaluationfunction) == "function") {
val.isvalid = val.evaluationfunction(val);
if (!val.isvalid && Page_InvalidControlToBeFocused == null &&
typeof(val.focusOnError) == "string" && val.focusOnError == "t") {
ValidatorSetFocus(val, event);
}
}
}
ValidatorUpdateDisplay(val);
}
Note the call to ValidatorSetFocus, which is a rather long method that attempts to set the focus to the control in question, or if you have multiple errors, to the last control that was validated, using (eventually) the following lines:
if (typeof(ctrl.focus) != "undefined" && ctrl.focus != null) {
ctrl.focus();
Page_InvalidControlToBeFocused = ctrl;
}
To get this behaviour to work, you would ideally need to ensure that all your validators are set to be client-side - server side validators will obviously require a postback, and that might affect things (i.e. lose focus/position) - and setting MaintainScrollPositionOnPostBack to true would probably cause the page to reload to the submit button, rather than the invalid form element.
Using the server side .Focus method will cause ASP.NET to render out some javascript "on the page load" (i.e. near the bottom of the page) but this could be being overriden by one of the other mechanisms dicussed above.
SO I believe the problem is because I was trying to focus on HtmlGenericControls instead of WebControls.
I just ended up doing a workaround based off of:
http://ryanfarley.com/blog/archive/2004/12/21/1325.aspx
http://www.codeproject.com/KB/aspnet/ViewControl.aspx
...in the interest of time.
public static void ScrollTo(this HtmlGenericControl control)
{
control.Page.RegisterClientScriptBlock("ScrollTo", string.Format(#"
<script type='text/javascript'>
$(document).ready(function() {{
var element = document.getElementById('{0}');
element.scrollIntoView();
element.focus();
}});
</script>
", control.ClientID));
}
Usage:
if (!this.PropertyForm.Validate())
{
this.PropertyForm.ErrorMessage.ScrollTo();
failed = true;
}
(Although it appears Page.RegisterClientScriptBlock() is deprecated for Page.ClientScript.RegisterClientScriptBlock()).
Adding MaintainScrollPositionOnPostback is the closest that ASP.NET has built in, but won't necessarily jump to the invalid field(s).
<%# Page MaintainScrollPositionOnPostback="true" %>
Very simple solution is to set the SetFocusOnError property of the RequiredFieldValidator (or whichever validator control you are using) to true
Are you sure Focus() won't do what you're describing? Under the hood, it is essentially doing the "JavaScript workaround" - it writes some JS to the page which calls focus() on the control with the matching ID:
Whichever control had Focus() called last before the page finishes processing writes this to the page:
<script type="text/javascript">
//<![CDATA[
WebForm_AutoFocus('txtFocus2');//]]>
</script>
Please insert these into your OnClick event
Page.MaintainScrollPositionOnPostBack = false;
Page.SetFocus("cliendID");
// or
Page.setFocus(control);
You should looks into jQuery and the ScrollTo plugin
http://demos.flesler.com/jquery/scrollTo/
I've achieved something similar using basic HTML fragments. You just leave an element with a known ID:
<span id="CONTROL-ID"></span>
And then either via script, on on the server side change the url:
window.location += "#CONTROL-ID";
In the first case the page won't reload, it will just scroll down to the control.
Paste the following Javascript:
function ScrollToFirstError() {
Page_ClientValidate();
if (Page_IsValid == false) {
var topMostValidator;
var lastOffsetTop;
for (var i = 0; i < Page_Validators.length; i++) {
var vld = Page_Validators[i];
if (vld.isvalid == false) {
if (PageOffset(vld) < lastOffsetTop || lastOffsetTop == undefined) {
topMostValidator = vld;
lastOffsetTop = vld.offsetTop;
}
}
}
topMostValidator.scrollIntoView();
}
return Page_IsValid;
}
function PageOffset(theElement) {
var selectedPosY = 0;
while (theElement != null) {
selectedPosY += theElement.offsetTop;
theElement = theElement.offsetParent;
}
return selectedPosY;
}
Then call ScrollToFirstError() in your OnClientClick of the button that is saving, make sure the button has CausesValidation=true as well.
There you have it.
Related
So i have a ASP.NET 4 Custom Control called "SafeClickButton" which is designed to override the default behaviour of the client-side click (OnClientClick).
Essentially i'm trying to disable the button on click, then do any existing functionality (validation, postback, etc).
It looks to be correctly rendering the HTML (onclick="this.disabled=true;__doPostback...), and it is disabling correctly, but the problem is with the page validation. If any validation on the page has failed, its posting back and THEN showing the validation errors (where it should be done on client side without requiring a postback).
Here's the code for the custom control.
public class SafeClickButton : Button
{
public override string OnClientClick
{
get
{
return string.Format("this.disabled=true;{0}", Page.ClientScript.GetPostBackEventReference(this, string.Empty));
}
set
{
base.OnClientClick = value;
}
}
protected override PostBackOptions GetPostBackOptions()
{
PostBackOptions options = new PostBackOptions(this, string.Empty) {ClientSubmit = true};
if (Page != null)
{
if (CausesValidation && (Page.GetValidators(ValidationGroup).Count > 0))
{
options.PerformValidation = true;
options.ValidationGroup = ValidationGroup;
}
if (!string.IsNullOrEmpty(PostBackUrl))
{
options.ActionUrl = HttpUtility.UrlPathEncode(ResolveClientUrl(PostBackUrl));
}
}
return options;
}
}
What am i doing wrong?
EDIT
Okay so i found part of the problem:
return string.Format("this.disabled=true;{0}", Page.ClientScript.GetPostBackEventReference(this, string.Empty));
Will not apply the dervied behaviour of the postbackoptions.
So i changed it to this:
return string.Format("this.disabled=true;{0}", Page.ClientScript.GetPostBackEventReference(GetPostBackOptions()));
Now the validation is getting fired properly on the client side, but the button isn't re-enabled, FML =)
I think i need to be even smarted now, and say "If validation fails, re-enable button".
Any ideas?
You should be able to just add the Page_ClientValidate method inline. I have never tried this so it might not work:
return string.Format("if (Page_ClientValidate()) { this.disabled=true; {0} } else return false;",
Page.ClientScript.GetPostBackEventReference(this, string.Empty));
You might have to mess with it or add some checks to support GroupValidation but I think this will get you on the right path.
EDIT: I have updated the answer and moved you disable into the if so it only gets disabled when Page_ClientValidate fails.
Check out this link as it is doing what you are looking for I think and illustrates what I was meaning with the Page_ClientValidate:
http://msmvps.com/blogs/anguslogan/archive/2004/12/22/27223.aspx
On load I'm both calling a JavaScript setTimeout() function that will hide a .NET Panel control, and hiding it in the code behind on first load. Clicking the save button will set the Panel to visible then reload the page at which point a setTimeout() function is called... so basically you click save, and see a panel with "Details Saved" for three seconds, at which point it disappears.
The problem is the external JavaScript file can't find _pDivAlert.ClientID (I've debugged and it returns null). It only works when the code is in a tag in the .aspx page. Any suggestions as to how I can either pass the client ID to the HideControl() function or find the ClientID from the external JS file?
Here's my code, any suggestions?
<script language="javascript" src="Forms.js" type="text/javascript"></script>
<body onload="ToggleAlert()">
<form id="form1" runat="server">
<script type="text/javascript">
//alert the user that the details were saved
function HideControl() {
var control = document.getElementById('<%=_pDivAlert.ClientID %>');
if(control != null)
control.style.display = 'none';
}
function ToggleAlert() {
setTimeout("HideControl()", 3000);
}
</script>
I've also tried sending the ClientID within the ToggleAlert() call, but that didn't work:
<body onload="ToggleAlert('<%=_pDivAlert.ClientID %>')">
External JS:
function HideControl(_c) {
var control = _c;
if (control != null)
control.style.display = 'none';
}
function ToggleAlert(_c) {
setTimeout("HideControl(_c)", 3000);
}
can you show your markup with the panel and the codebehind where you hide it?
there's a difference between setting the Visible property to false and setting the style display attribute to none- the first will not render the element at all, meaning there isn't anything rendered with the id you're looking for.
edit: it's probably because of the way you're calling HideControl in the timeout- this should be a function instead of a string.
try doing
function ToggleAlert(_c) {
setTimeout(
function () {
HideControl(_c);
}, 3000);
}
just for clarity, when you pass a string to setTimeout, it's evaluated and then run. the code chunk that eval produces will run in a different scope than your ToggleAlert method, and so _c won't be available at that time.
edit: you also need to actually get a reference to the control. you're passing the id string to ToggleAlert, which relays it to HideControl, which is expecting an object not a string.
function HideControl(_c) { // _c is the id of the element
var control = document.getElementById(_c);
if (control != null)
control.style.display = 'none';
}
I have a C# ASP.NET web page with an xml file upload form. When the user clicks 'upload', a javascript confirm alert will pop up asking the user, "is this file correct?". The confirm alert will only activate if the file name does not contain a value from one of the other form fields.
What is the best way to combine the use of a C# ASP.NET form and a javascript confirm alert that is activated if the name of a file being uploaded does not meet certain criteria?
There's not much you need to do with C# for this page, it sounds like most of this will be done on the client side.
Add the fileupload control and a button to your .aspx form. Set the Button's OnClientClick property to something like
OnClientClick = "return myFunction()"
and then write a javascript function like:
function myFunction()
{
// Check other text values here
if (needToConfirm){
return confirm('Are you sure you want to upload?');
}
else return true;
}
Make sure "myFunction()" returns false if you wish to cancel the postback (i.e. the user clicked "no" in the confirm dialog). This will cancel the postback if they click "No".
I suppose you are putting value of valid string in a hidden field (you haven't mentioned). Implement OnClientClick for Upload button:
<asp:button .... OnClientClick="return confirmFileName();"/>
<script type="text/javascript">
function confirmFileName()
{
var f = $("#<%= file1.ClientID %>").val();
var s=$("#<%= hidden1.ClientID %>").attr("value");
if (f.indexOf(s) == -1) {
if (!confirm("Is this correct file?")) {
$("#<%=file1.ClientID %>").focus();
return false;
}
}
return true;
}
</script>
EDIT:- Regarding <%= file1.ClientID %>.
This will be replaced by the client side ID of the file upload control like ctl00$ctl00$cphContentPanel$file1. It puts the script on steroids with respect to using something like $("input[id$='file1']"). For more information please see Dave Wards' post.
window.onload = function() {
document.forms[0].onsubmit = function() {
var el = document.getElementById("FileUpload1");
var fileName = el.value;
if(fileName.indexOf("WHATEVER_VALUE") == -1) {
if(!confirm("Is the file correct?")) {
el.focus();
return false;
}
}
return true;
}
}
I had problems implementing this kind of thing to work in both IE and FireFox because of the way events work in those browsers. When I got it to work in one of them, the other would still cause a postback even if I cancelled out.
Here's what we have in our code (the browser test was stolen from elsewhere).
if (!window.confirm("Are you sure?"))
{
if (/MSIE (\d+\.\d+);/.test(navigator.userAgent))
window.event.returnValue = false;
else
e.preventDefault();
}
In addition to using client side validation, you should also add a CustomValidator to provide validation on the server side. You cannot trust that the user has Javascript turned on, or that the user has not bypassed your Javascript checks.
Is there a way to check if a ValidationSummary control has its IsValid property set to true using Javascript in the OnClientClick event of a button?
What I'm trying to do is to show a message that says "please wait while your file is uploading" on an upload page, but if I use javascript to show that message, it shows up even when the ValidationSummary has errors, so the message shows up along with the errors underneath, which confuses users.
If you have several validation groups on single page then you should check only certain group:
var isValid = Page_ClientValidate('GroupName');
I think this will do what you want.
var isValid = false;
if (typeof(Page_ClientValidate) == 'function')
{
isValid = Page_ClientValidate();
}
if(isValid)
{
ShowMessage(...);
}
In case others need something like this, here is my solution:
In the button's OnClientClick event, I'm calling a javascript function called showContent(). In this function, I use setTimeout to call a second function that checks the page's IsValid property:
function showContent()
{
setTimeout("delayedShow()", 1);
}
function delayedShow()
{
if (Page_IsValid != null && Page_IsValid == true)
{
document.getElementById('divUploading').style.display = "block";
}
}
The Page_IsValid returns true in the OnClientClick event because the javascript validation runs after this, so the 1 second delay allows the IsValid property to be properly set.
I guess what you should do is dissable the uppload button and show a message while upload is in progress. For example by using an ajax panel and an progress template.
On my submit button, what I'd like to do is OnClick show a "Please wait" panel and hide the button, UNLESS the validators say something's invalid - then I need the buttons still showing obviously. Otherwise I have a validation summary showing erros and no way to submit again.
Most articles I find about doing this want to use Page_ClientValidate() function to tell the page to validate itself, but this comes back undefined for me, as does Page_IsValid variable. Here is the function I'm trying to use - what am I missing?:
function PleaseWaitShow() {
try {
alert("PleaseWaitShow()");
var isPageValid = true;
// Do nothing if client validation is not active
if (typeof(Page_Validators) == "undefined") {
if (typeof(Page_ClientValidate) == 'function') {
isPageValid = Page_ClientValidate();
alert("Page_ClientValidate returned: " + isPageValid);
alert("Page_IsValid=" + Page_IsValid);
} else {
alert("Page_ClientValidate function undefined");
}
} else {
alert("Page_Validators undefined");
}
if(isPageValid) {
// Hide submit buttons
document.getElementById('pnlSubmitButton').style.visibility = 'hidden';
document.getElementById('pnlSubmitButton').style.display = 'none';
// Show please wait panel
document.getElementById('pnlPleaseWait').style.visibility = 'visible';
document.getElementById('pnlPleaseWait').style.display = 'block';
} else {
alert("page not valid - don't show please wait");
}
} catch(er) {
alert("ERROR in PleaseWaitShow(): " + er);
}
}
change this line "if (typeof(Page_Validators) == "undefined") " to
if (typeof(Page_Validators) != "undefined")
According to the section "The Client-Side API" on the page "ASP.NET Validation in depth":
Page_IsValid | Boolean variable | Indicates whether the page is currently valid. The validation scripts keep this up to date at all times.
Indeed, watching this variable in FireBug on a form with ASP.NET client side validation enabled, it does get updated as I fill in details of the form (incorrectly, or correctly).
Obviously, if you've disabled client script on your validators or the validation summary, then this variable won't be available to you.
Just check
if(Page_IsValid)
{
//Yourcode
}
This works if you have validators in the page, which excludes the validation summary.
Page_ClientValidate() is not any standard javascript function i know of
I believe I've found a "kind of" answer.
I still cannot identify why my page will not identify "Page_ClientValidate()" or "Page_IsValid" - this part is still unanswered.
However, I am using a number of PeterBlum validators on the page, and those do provide a "VAM_ValOnSubmit()" that returns true/false. So this may be the solution. I might just have to be sure all the validators are PeterBlum to catch them all.
Not the greatest solution, but better than I've gotten so far. I'm still open to answers on the "Page_IsValid" portion.
There's an ASP.Net forum thread on this topic: Button that prevents multiple clicks
Here's the solution (in code behind):
private void BuildClickOnceButton(WebControl ctl)
{
System.Text.StringBuilder sbValid = new System.Text.StringBuilder();
sbValid.Append("if (typeof(Page_ClientValidate) == 'function') { ");
sbValid.Append("if (Page_ClientValidate() == false) { return false; }} ");
sbValid.Append(ctl.ClientID + ".value = 'Please wait...';");
sbValid.Append(ctl.ClientID + ".disabled = true;");
//GetPostBackEventReference obtains a reference to a client-side script function that causes the server to post back to the page.
sbValid.Append(ClientScript.GetPostBackEventReference(ctl, ""));
sbValid.Append(";");
ctl.Attributes.Add("onclick", sbValid.ToString());
}