customvalidator based on visibility of control - asp.net

I have the custom validator code
<asp:CustomValidator ID="ddl1" ClientValidationFunction="IscontrolVisible" ControlToValidate="ddl1"
runat="server" ValidationGroup="ValSummary" Display="Dynamic" ErrorMessage="drop down invisible">*</asp:CustomValidator>
below is the function
function IscontrolVisible(source, arguments) {
//if ((document.getElementById(source).visibility == "visible")) {
debugger;
if (arguments.value == "" || arguments.value == "Select...") {
arguments.IsValid = false;
}
else {
arguments.IsValid = true;
}
}
how can i get the error message based on the visibility of the control?

Use the source.controltovalidate property to get the ID of the control being validated, so use document.getElementById(source.controltovalidate) in your code.
However, you will only be able to detect whether the control you are validating on is visible if you are directly setting the visibility on the control itself.
In which case, you can do something like one of the following...
if (document.getElementById(source.controltovalidate).style.visibility !== "false") {
if (document.getElementById(source.controltovalidate).style.display !== "none") {
If the control is not visible because of one of it's parent controls, then it does NOT inherit the CSS styling of visibility or display. In which case you would have to recursively check all the ancestors of the control in question.
Additional
If you want to change the ErrorMessage text (note this will only change it locally on the browser, and will NOT be replicated back on the server after a postback), then the following should work for the validation summary message box popup and list/bullets...
source.errormessage = "New Error Message";
And the following should work for the message when shown as part of the validator itself...
source.innerHTML = "New Error Message";

Related

ASP.NET Custom Button Control - How to Override OnClientClick But Preserve Existing Behaviour?

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

Jquery datepicker popup not closing on select date in IE8

I've got a web form with a start date field. I've tied a jquery datepicker to the txt field. Now when I choose a date in FF, the selected date is populated in the text box and the calendar popup closes. However when I do the same thing in IE8, the selected date is populated in the text box but the popup remains open. I've also noticed that a script error is generated as soon as I select a date in the popup calendar.
I'm using jquery 1.3.2, jquery-ui 1.7.2, and .NET 3.5. Here's an example of my code:
<script type="text/javascript">
$(document).ready(function() {
$("#<%=txtStartDate.ClientID%>").datepicker({
changeMonth: true,
changeYear: true,
showButtonPanel: true,
showOn: 'button',
buttonImage: '/_layouts/images/CALENDAR.GIF',
buttonImageOnly: true
});
});
</script>
<div id="stylized">
<asp:ValidationSummary ID="vs" runat="server" CssClass="messages-error" HeaderText=" Action required before the form can be submitted." ForeColor="" ValidationGroup="sh" />
<div class="formrow">
<div class="ms-formlabel formlabel">
<asp:Label ID="lblStartDate" runat="server" CssClass="ms-standardheader" AssociatedControlID="txtStartDate">Start Date:</asp:Label>
</div>
<div class="ms-formbody formfield">
<asp:RequiredFieldValidator ID="reqStartDate" runat="server" ControlToValidate="txtStartDate" ErrorMessage="Start Date is a required field." Text="*" Display="Dynamic" ValidationGroup="sh"></asp:RequiredFieldValidator>
<asp:CompareValidator ID="cvStartDate" runat="server" ControlToValidate="txtStartDate" ErrorMessage="Date must be in the format MM/DD/YYYY" Text="*" Display="Dynamic" ValidationGroup="sh" Operator="DataTypeCheck" Type="Date"></asp:CompareValidator>
<asp:TextBox ID="txtStartDate" runat="server"></asp:TextBox>
<span class="formMessage">ex. MM/DD/YYYY</span>
</div>
</div>
<div id="buttonrow">
<asp:Button ID="btnSubmit" runat="server" Text="Submit" CssClass="ms-ButtonHeightWidth" OnClick="Submit_Click" ValidationGroup="sh" />
<asp:Button ID="btnCancel" runat="server" Text="Cancel" CssClass="ms-ButtonHeightWidth" OnClick="Cancel_Click" CausesValidation="false" />
</div>
</div>
Here's the script error I get in IE when I select the date:
'length' is null or not an object
WebResource.axd
Here's the code where the error is being thrown from:
function ValidatorOnChange(event) {
if (!event) {
event = window.event;
}
Page_InvalidControlToBeFocused = null;
var targetedControl;
if ((typeof(event.srcElement) != "undefined") && (event.srcElement != null)) {
targetedControl = event.srcElement;
}
else {
targetedControl = event.target;
}
var vals;
if (typeof(targetedControl.Validators) != "undefined") {
vals = targetedControl.Validators;
}
else {
if (targetedControl.tagName.toLowerCase() == "label") {
targetedControl = document.getElementById(targetedControl.htmlFor);
vals = targetedControl.Validators;
}
}
var i;
for (i = 0; i < vals.length; i++) {
ValidatorValidate(vals[i], null, event);
}
ValidatorUpdateIsValid();
}
It happens on the .length in the for loop at the end. Vals is null and isn't found in the previous if/else. I've stepped through the javascript and if (typeof(targetedControl.Validators) != "undefined") returns false and then if (targetedControl.tagName.toLowerCase() == "label") returns false too. Thus the length is null or not an object error.
Now I'm not sure if the datepicker popup not closing in IE and the script error in the WebResources.axd file are related errors, but I'm leaning that way. Can anyone tell me why the popup isn't closing?
As a date is selected, the datepicker triggers the change event on the INPUT element, but the ASP.Net validator picks up the click event instead, with the source an A element, and tries to find the validators on that A element, instead of the INPUT. This can be observed by inspecting event.srcElement inside the validator's ValidatorOnChange function. In browsers other than IE, event.type is 'change' and event.target is correctly the INPUT.
While the no-op function onSelect: function() { } prevents the error, by overriding the .change() built-in to the datepicker's default onSelect, it also prevents the validators from triggering. Here's a work-around for both:
onSelect: function() {
this.fireEvent && this.fireEvent('onchange') || $(this).change();
}
This uses the normal .change() trigger except on IE, where it's required to use .fireEvent to get the event object to be associated with the change and not the click.
It seems to be a bug of sorts, but adding this line in the datepicker declaration should solve it:
onSelect: function() {}
The solutions provided above only prevents the error from occurring.
On the datepicker:
onSelect : function(dateText, inst){ inst.input.trigger('cValidate')
and bind the event to the calendar input element.
.bind('cValidate', function (event) { window.ValidatorOnChange(event); });
this will fire the validatorchanged event with the correct event args (input field).
The reason
The root bug (I think it's probably meant to be a feature, or maybe a workaround for a known IE bug?) is in ASP.Net's ValidatorHookupEvent:
var func;
if (navigator.appName.toLowerCase().indexOf('explorer') > -1) {
func = new Function(functionPrefix + " " + ev);
}
else {
func = new Function("event", functionPrefix + " " + ev);
}
As a result, in the default case that there's no other onchange registered, this sets up the onchange for the input to be the equivalent of
function() { ValidatorOnChange(event); }
in IE and
function(event) { ValidatorOnChange(event); }
in other browsers. So iff you're using IE, the event passed to ValidatorOnChange will be window.event (since window is the global object).
A suggested solution
If you don't want to hack around with the ASP.Net scripts then I think the nicest way to handle this is to detect the broken event handler and replace it. As with other suggestions here, I offer an onSelect to include in the datepicker options.
onSelect: function(dateStr, datePicker) {
// http://stackoverflow.com/questions/1704398
if (datePicker.input[0].onchange.toString().match(/^[^(]+\(\)\s*{\s*ValidatorOnChange\(event\);\s*}\s*$/)) {
datePicker.input[0].onchange = ValidatorOnChange;
}
datePicker.input.trigger("change");
}
I'm applying this globally with
$.datepicker.setDefaults({
onSelect: function(dateStr, datePicker) {
etc.
}
});
It doesn't look like you're doing anything wrong since the ValidatorOnChange code is generated for you; there's something wrong in the way it's creating its vals object which appears to end up null on ie8.
It's been asked before, and the solution is overriding the onSelect function with a no-op function.
This is not the only kind of validator problem out there. Here's a vaguely similar issue with the autocomplete feature.
The fix...
onSelect: function() {}
..does not appear to work if the problem is with a CustomValidator that relies on a servewr side event handler to validate input.
There are a couple of other fixes mentioned here...
http://dev.jqueryui.com/ticket/4071
The problem is down to IE's event handling differing from other browsers and the client side validation code supplied by ASP Net not reacting gracefully to a situation not contemplated by it's authors.
This is an endemic problem with jQuery datepickers and ASP validation controls.
As you are saying, the wrong element cross-triggers an ASP NET javascript validation routine, and then the M$ code throws an error because the triggering element in the routine is undefined.
I solved this one differently from anyone else I have seen - by deciding that M$ should have written their code more robustly, and hence redeclaring some of the M$ validator code to cope with the undefined element. Everything else I have seen is essentially a workaround on the jQuery side, and cuts possible functionality out (eg. using the click event instead of change).
The bit that fails is
for (i = 0; i < vals.length; i++) {
ValidatorValidate(vals[i], null, event);
}
which throws an error when it tries to get a length for the undefined 'vals'.
I just added
if (vals) {
for (i = 0; i < vals.length; i++) {
ValidatorValidate(vals[i], null, event);
}
}
and she's good to go. Final code, which redeclares the entire offending function, is below. I put it as a script include at the bottom of my master page or page (so it occurs after the default declarations and replaces the earlier version).
Yes, this does break upwards compatibility if M$ decide to change their validator code in the future. But one would hope they'll fix it and then we can get rid of this patch altogether.
// Fix issue with datepicker and ASPNET validators: redeclare MS validator code with fix
function ValidatorOnChange(event) {
if (!event) {
event = window.event;
}
Page_InvalidControlToBeFocused = null;
var targetedControl;
if ((typeof (event.srcElement) != "undefined") && (event.srcElement != null)) {
targetedControl = event.srcElement;
}
else {
targetedControl = event.target;
}
var vals;
if (typeof (targetedControl.Validators) != "undefined") {
vals = targetedControl.Validators;
}
else {
if (targetedControl.tagName.toLowerCase() == "label") {
targetedControl = document.getElementById(targetedControl.htmlFor);
vals = targetedControl.Validators;
}
}
var i;
if (vals) {
for (i = 0; i < vals.length; i++) {
ValidatorValidate(vals[i], null, event);
}
}
ValidatorUpdateIsValid();
}
change jquery.ui.datepicker.js line 1504
'" href="#" >' + printDate.getDate() + '</a>')
with
'" href="javascript:DP_jQuery_' + dpuuid + '.datepicker._selectDay(\'#' +
inst.id + '\',' + printDate.getMonth() + ',' + printDate.getFullYear() + ', this);" >' + printDate.getDate() + '</a>')
test works OK!
I don't really know what the problem is, but I did notice that your does not have the ValidationGroup set, and both of your validators have that value set. You might try setting ValidationGroup="sh" in your TextBox and see if that helps.
Building upon the above answers and to provide further detail to my comment above,
Apparently in IE9+ and other browsers, you should now use dispatchEvent to fire the change event. (Why does .fireEvent() not work in IE9?)
The OnSelect function in the datepicker actually has 2 arguments:
The value of the textbox associated with the datepicker
An object with an id property that matches that of the textbox
mytextbox.datepicker({
defaultDate: null,
numberOfMonths: 1,
dateFormat: DisplayDateFormat,
onSelect: function (value, source) {
}
});
All the examples I saw used document.getElementById() to retrieve the textbox, which I thought wouldn't be necessary seeing as the source object has the same id as the textbox. Upon closer examination it turns out that source is an object, not the textbox element. I found the following code resolved the problem though:
mytextbox.datepicker({
defaultDate: null,
numberOfMonths: 1,
dateFormat: DisplayDateFormat,
onSelect: function (value, source) {
var ctrl = document.getElementById(source.id);
if ("dispatchEvent" in ctrl) {
// IE9
var evt = document.createEvent("HTMLEvents");
evt.initEvent("change", false, true);
ctrl.dispatchEvent(evt);
} else if ("fireEvent" in ctrl) {
// IE7/IE8
ctrl.fireEvent("onchange");
} else {
$(ctrl).change();
}
}
});
Update: It appears that this approach is no longer working - not sure why. It stops the error from being thrown but doesn't trigger the change event.

ASP.NET: Scroll to control

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.

Check if ValidationSummary is Valid using OnClientClick

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.

Javascript checking if page is valid

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

Resources