How to validate against Multiple validation groups? - asp.net

I have two validation groups: parent and child
I have an add button that needs to only validate the child validation group which is easily done. The save button needs to validate against the parent and child validation groups, both client side and server side. I think I know how to do it server side by calling the Page.Validate("groupname") method for each group, but how can it be done client side?

You should be able to accomplish this by creating a javascript function that uses Page_ClientValidate and then having the button call that function
<asp:Button ID="btnSave" Text="Save" OnClientClick="return validate()" runat="server" />
<script type="text/javascript">
function validate() {
var t1 = Page_ClientValidate("parent");
var t2 = Page_ClientValidate("child");
if (!t1 || !t2) return false;
return true;
}
</script>

The problem with CAbbott's answer is that validation errors that occur in the "parent" group will not be displayed after the call to validate the "child" group. The more minor problem with Oleg's answer is that validation of the "child" group will not occur until the "parent" group is ready.
All we really need to do to allow client-side validation of more than one group at the same time is to override the Javascript IsValidationGroupMatch method which determines whether or not a control is to be included in the current set being validated.
For example:
(function replaceValidationGroupMatch() {
// If this is true, IsValidationGroupMatch doesn't exist - oddness is afoot!
if (!IsValidationGroupMatch) throw "WHAT? IsValidationGroupmatch not found!";
// Replace ASP.net's IsValidationGroupMatch method with our own...
IsValidationGroupMatch = function(control, validationGroup) {
if (!validationGroup) return true;
var controlGroup = '';
if (typeof(control.validationGroup) === 'string') controlGroup = control.validationGroup;
// Deal with potential multiple space-delimited groups being validated
var validatingGroups = validationGroup.split(' ');
for (var i = 0; i < validatingGroups.length; i++) {
if (validatingGroups[i] === controlGroup) return true;
}
// Control's group not in any being validated, return false
return false;
};
} ());
// You can now validate against multiple groups at once, for example:
// space-delimited list. This would validate against the Decline group:
//
// Page_ClientValidate('Decline');
//
// while this would validate against the Decline, Open and Complete groups:
//
// Page_ClientValidate('Open Decline Complete');
//
// so if you wanted to validate all three upon click of a button, you'd do:
<asp:Button ID="yourButton" runat="server"
OnClick="ButtonSave_Click" CausesValidation="false"
OnClientClick="return Page_ClientValidate('Open Decline Complete');" />

If you call Page_ClientValidate(..) twice, only the last validation result will be shown and it can be OK while the first is not. So the second call should be made only if the first has returned true
<script type="text/javascript">
var parentOk= Page_ClientValidate('parent');
var childOk = false;
if (parentOk) {
childOk = Page_ClientValidate('child');
}
return parentOk && childOk;
</script>

Whatever way you do it requires some hacking to get round ASP.Net's assumption that you wouldn't try to do this. I favour a reusable approach which is explicit about the hackery involved.
using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebSandbox.Validators
{
/// <summary>
/// <para>
/// Validates a different validation group. Among the use cases envisioned are
/// <list type="">
/// <item>
/// Validating one set of rules when the user clicks "Save draft" and validating those rules plus some
/// extra consistency checks when they click "Send".
/// </item>
/// <item>
/// Grouping controls in a <code>fieldset</code> into a validation group with a
/// <code>ValidationSummary</code> and then having a final <code>ValidationSummary</code> which tells the
/// user which groups still have errors.
/// </item>
/// </list>
/// </para>
/// <para>
/// We include checks against setting <code>GroupToValidate</code> to the same value as
/// <code>ValidationGroup</code>, but we don't yet include checks for infinite recursion with one validator
/// in group A which validates group B and another in group B which validates group A. Caveat utilitor.
/// </para>
/// </summary>
public class ValidationGroupValidator : BaseValidator
{
public string GroupToValidate
{
get { return ViewState["G2V"] as string; }
set { ViewState["G2V"] = value; }
}
protected override bool ControlPropertiesValid()
{
if (string.IsNullOrEmpty(GroupToValidate)) throw new HttpException("GroupToValidate not specified");
if (GroupToValidate == ValidationGroup) throw new HttpException("Circular dependency");
// Don't call the base, because we don't want a "control to validate"
return true;
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
writer.AddAttribute("evaluationfunction", "ValidateValidationGroup");
writer.AddAttribute("GroupToValidate", GroupToValidate);
}
protected override void OnPreRender(EventArgs e)
{
// The standard validation JavaScript is too restrictive for this validator to work, so we have to replace a key function.
// Fortunately this runs later than the standard JS, so we can simply overwrite the existing value of Page_ClientValidate.
Page.ClientScript.RegisterStartupScript(typeof(ValidationGroupValidator), "validationJS", _ValidationJS);
base.OnPreRender(e);
}
protected override bool EvaluateIsValid()
{
if (string.IsNullOrEmpty(GroupToValidate)) return false;
bool groupValid = true;
foreach (IValidator validator in Page.GetValidators(GroupToValidate))
{
validator.Validate();
groupValid &= validator.IsValid;
}
return groupValid;
}
private const string _ValidationJS = #"<script type=""text/javascript"">
function ValidateValidationGroup(val) {
if (typeof(val.GroupToValidate) == ""string"") {
val.valid = PageMod_DoValidation(val.GroupToValidate);
}
}
function Page_ClientValidate(validationGroup) {
Page_InvalidControlToBeFocused = null;
if (!Page_Validators) return true;
var i, ctrl;
// Mark everything as valid.
for (i = 0; i < Page_Validators.length; i++) {
Page_Validators[i].finalValid = true;
}
if (Page_ValidationSummaries) {
for (i = 0; i < Page_ValidationSummaries.length; i++) {
Page_ValidationSummaries[i].finalDisplay = ""none"";
}
}
// Validate.
var groupValid = PageMod_DoValidation(validationGroup);
// Update displays once.
for (i = 0; i < Page_Validators.length; i++) {
ctrl = Page_Validators[i];
ctrl.isvalid = ctrl.finalValid;
ValidatorUpdateDisplay(ctrl);
}
if (Page_ValidationSummaries) {
for (i = 0; i < Page_ValidationSummaries.length; i++) {
ctrl = Page_ValidationSummaries[i];
ctrl.style.display = ctrl.finalDisplay;
}
}
ValidatorUpdateIsValid();
Page_BlockSubmit = !Page_IsValid;
return Page_IsValid;
}
function PageMod_DoValidation(validationGroup) {
var groupValid = true, validator, i;
for (i = 0; i < Page_Validators.length; i++) {
validator = Page_Validators[i];
ValidatorValidate(validator, validationGroup, null);
validator.finalValid &= validator.isvalid;
groupValid &= validator.isvalid;
}
if (Page_ValidationSummaries) {
ValidationSummaryOnSubmit(validationGroup, groupValid);
var summary;
for (i = 0; i < Page_ValidationSummaries.length; i++) {
summary = Page_ValidationSummaries[i];
if (summary.style.display !== ""none"") summary.finalDisplay = summary.style.display;
}
}
return groupValid;
}
</script>";
}
}

Related

ClickSuppressor in Web Applications

for my Winform applications I have used ClickSuppressor so that pereventing multiple action by pressing button twice or more. I am wondering what about the WebForms and MVC applications? Do we also need to use ClickSuppressor in this kind of projects?
using (new MyClass.ClickSuppressor(this))
{
try
{
//my action code i.e. add record
}
}
MyClass:
/// <summary>
/// Stops to queue event in order to prevent from "multiple click" when adding a new record.
/// </summary>
internal class ClickSuppressor : IDisposable
{
private Control mCtrl;
public ClickSuppressor(Control ctrl)
{
mCtrl = ctrl;
mCtrl.Enabled = false;
mCtrl.Update();
}
public void Dispose()
{
Application.DoEvents();
/* Or this:
MethodInfo mi = typeof(Control).GetMethod("RemovePendingMessages", BindingFlags.Instance | BindingFlags.NonPublic);
mi.Invoke(mCtrl, new object[] { 0x201, 0x203 });
mi.Invoke(mCtrl.Parent, new object[] { 0x201, 0x203 });
*/
if (!mCtrl.IsDisposed) mCtrl.Enabled = true;
}
}
This needs to be done in the browser with scripts.
Set the disabled attribute on the element to "disabled" when it is clicked:
<button class="singleClick">
The jQuery:
$(".singleClick").on("click", function(){
this.attr("disabled", "disabled");
});
You can remove the disabled status later with:
$(element).removeAttr("disabled");

any easy way to check if no items has been selected in a checkboxlist control?

I know I can loop through a checkboxlist.Items and see if none of them is selected, but is there a better way to find out if no items have been selected, in the code behind?
You have to check the SelectedIndex. If it equals -1, its means nothing is selected.
CheckBoxList list = new CheckBoxList();
if (list.SelectedIndex == -1)
{
//Nothing is selected
}
CheckBoxList has properties for SelectedIndex and SelectedValue.
You can check whether there is a SelectedIndex; it will be -1 if no items are checked.
This is probably the easiest way to validate the CheckBoxList:
Use a custom validator:
<asp:CustomValidator runat="server" ID="cvmodulelist" ClientValidationFunction="ValidateModuleList" ErrorMessage="Please Select Atleast one Module"></asp:CustomValidator>
Create a JavaScript function to validate the CheckBoxList:
// javascript to add to your aspx page
function ValidateModuleList(source, args)
{
var chkListModules= document.getElementById ('<%= chkModuleList.ClientID %>');
var chkListinputs = chkListModules.getElementsByTagName("input");
for (var i=0;i<chkListinputs .length;i++)
{
if (chkListinputs [i].checked)
{
args.IsValid = true;
return;
}
}
args.IsValid = false;
}
Another option is to create a custom validation control, like this:
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.ComponentModel;
namespace CustomValidators
{
public class RequiredFieldValidatorForCheckBoxLists : System.Web.UI.WebControls.BaseValidator
{
private ListControl _listctrl;
public RequiredFieldValidatorForCheckBoxLists()
{
base.EnableClientScript = false;
}
protected override bool ControlPropertiesValid()
{
Control ctrl = FindControl(ControlToValidate);
if (ctrl != null)
{
_listctrl = (ListControl) ctrl;
return (_listctrl != null);
}
else
return false; // raise exception
}
protected override bool EvaluateIsValid()
{
return _listctrl.SelectedIndex != -1;
}
}
}
You have to check selected index. If selected index is -1 it means that no item is being selected.
if (CheckBoxList1.SelectedIndex == -1)
{
Response.Write("<script> alert('Show your message') </script>");
}

Unable to hook into PropertyChanged event using MVVM-Light

Greetings, creating my first MVVM based WPF app and trying to figure out why I'm unable to hook into the PropertyChanged event of a dependency property.
Code in the parent view model:
void createClients()
{
var clients = from client in Repository.GetClients()
select new ClientViewModel(Repository, client);
foreach (var client in clients)
{
client.PropertyChanged += onClientPropertyChanged;
}
Clients = new ViewableCollection<ClientViewModel>(clients);
Clients.CollectionChanged += onClientsCollectionChanged;
}
// Never gets called
void onClientPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Name")
{
//...
}
}
ViewableCollection is a simple extension of ObservableCollection to encapsulate a View.
In the ClientViewModel the setters are being called but RaisePropertyChanged isn't working as I would expect, because onClientPropertyChanged isn't being invoked. Both view models inherit from ViewModelBase.
public string Name
{
get { return client.Name; }
set
{
if (value == client.Name) return;
client.Name = value;
RaisePropertyChanged("Name");
}
}
If I wire up PropertyChanged to a method inside the ClientViewModel then it is being fired, so I'm stumped as to why this isn't working in the parent view model. Where am I going wrong?
This SO question explains the problem; ObservableCollection protects the PropertyChanged event.
One solution is to use MVVM-Light Messenger:
void createClients()
{
var clients = from client in Repository.GetClients()
select new ClientViewModel(Repository, client);
Clients = new ViewableCollection<ClientViewModel>(clients);
Clients.CollectionChanged += onClientsCollectionChanged;
Messenger.Default.Register<PropertyChangedMessage<string>>(this, (pcm) =>
{
var clientVM = pcm.Sender as ClientViewModel;
if (clientVM != null && pcm.PropertyName == "Name")
{
// ...
}
});
}
createClients() should be refactored, but for consistency with the question code I'll leave it in there. Then a slight change to the property setter:
public string Name
{
get { return client.Name; }
set
{
if (value == client.Name) return;
string oldValue = client.Name;
client.Name = value;
RaisePropertyChanged<string>("Name", oldValue, value, true);
}
}

ASP:TextBox Value disappears in postback only when password

I have an asp.net textbox like this:
<asp:TextBox ID="PINPad" runat="server" Columns="6" MaxLength="4"
CssClass="PINTextClass"></asp:TextBox>
It is, as you might have guessed, the text box from an on screen PIN pad. Javascript fills in the values. The page is posted back every five seconds (using an update panel if that matters) to update various other unrelated items on the screen. This works just fine.
However, when I convert it to a password text box, like this:
<asp:TextBox ID="PINPad" runat="server" Columns="6" MaxLength="4"
CssClass="PINTextClass" TextMode="Password"></asp:TextBox>
Then whenever the page posts back, the text box is cleared out on the screen and the textbox is empty (though during the timer event, the value does make it back to the server.)
Any suggestions how to fix this, so that it retains its value during postback?
As a security feature, ASP.NET tries to disallow you from sending the password value back to the client. If you're okay with the security issues (i.e. it's either not really secure information or you're sure that the connection is secure), you can manually set the "value" attribute of the control, rather than using its Text property. It might look something like this:
this.PINPad.Attributes.Add("value", this.PINPad.Text);
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
{
if (!(String.IsNullOrEmpty(txtPwd.Text.Trim())))
{
txtPwd.Attributes["value"]= txtPwd.Text;
}
if (!(String.IsNullOrEmpty(txtConfirmPwd.Text.Trim())))
{
txtConfirmPwd.Attributes["value"] = txtConfirmPwd.Text;
}
}
}
here is another way to do it:-
using System;
using System.Text;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace WebControlLibrary
{
public class PWDTextBox : TextBox
{
public PWDTextBox()
{
this.TextMode = TextBoxMode.Password;
}
public string Password
{
get
{
string val = (string)ViewState["pwd"];
if (string.IsNullOrEmpty(val))
{
return "";
}
else
{
return val;
}
}
set
{
ViewState["pwd"] = value;
}
}
public override string Text
{
get
{
return Password;
}
set
{
Password = value;
}
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
this.Text = Password;
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
base.AddAttributesToRender(writer);
writer.AddAttribute(HtmlTextWriterAttribute.Value, this.Password);
}
}
}
The problem of losing the password in the postback can be avoid making use of Asynchronous JavaScript calls, lets describe a typical scenario for a Login page:
Lets say we have a Login page which allows the user to change the language of its labels when the user choose a language with a dropdownlist
a solution would be to invoke selectedIndexChanged event of the dropdownlist, make a postback which goes to the server and picks up the labels in the chosen language.
in this scenario the field password will be lost due to the security feature of ASP.NET which makes passwords fields not persisted between a postbacks.
This scenario can be solved if the postback is avoided making use of Asynchronous JavaScript Technology and XML (Ajax) calls.
Add a javascript function which will be invoked from the dropdownlist control, in this case this function is assigned to the Command property of the dropdownlist in code behind:
function ValueChanged(div)
{
var table = div.getElementsByTagName("table");
if (table && table.length > 0)
{
var t = table[0].getAttribute('type');
if (t != null && (t == "DropDown"))
{
var inputs = div.getElementsByTagName("input");
if (inputs && inputs.length == 2)
{
{
Translate(inputs[1].value);
}
}
}
}
}
The Translate function takes as parameter the selected option language in the dropdown control and performs the asynchronous call as shown bellow.
function Translate(lang)
{
var request = null;
if (window.XMLHttpRequest)
{
request = new XMLHttpRequest();
if (request.overrideMimeType)
{
request.overrideMimeType('text/xml');
}
}
else if (window.ActiveXObject)
{
request = new ActiveXObject("Msxml2.XMLHTTP");
}
if (request == null)
{
return;
}
var url = "GetLoginTranslations.aspx";
request.open('GET', url +'?lang=' + lang, true);
request.setRequestHeader("Cache-Control", "no-cache");
request.setRequestHeader("Pragma", "no-cache");
request.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT");
request.onreadystatechange = function () { TranslateLabels(request); };
request.send(null);
}
the function Translate shown above performs the call and get the results in the specified .aspx page (in this case "GetLoginTranslations.aspx")
when the request is completed and the request.onreadystatechange is set to the function TranslateLabels this function will be executed.
on this way the postback is not executed as before in the event onSelectedIndexChanged of the dropdownlist control.
the TranslateLabels function would look something like :
function TranslateLabels(request)
{
if (request.readyState == 4)
{
if (request.status == 200)
{
if (request.responseXML)
{
var objRoot = request.responseXML.documentElement;
if (objRoot)
{
if (objRoot.nodeName == "strings")
{
for (var i = 0; i < objRoot.childNodes.length; i++)
{
var node = objRoot.childNodes[i];
var elem;
switch (node.getAttribute("id"))
{
case "lbl_login":
elem = document.getElementById("lbl_login");
if (elem)
elem.innerHTML = node.firstChild.nodeValue;
break;
}
///....
}
}
}
}
}
}
the request.responseXML contains the XML built in the page GetLoginTranslations.aspx and the structure of this XML is defined there.
the Page_Load() event in the GetLoginTranslations.aspx should look like:
protected void Page_Load(object sender, EventArgs e)
{
if (Request["lang"] != null)
strLang = Request["lang"];
//init response
Response.Clear();
Response.Cache.SetExpires(DateTime.Now);
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.Cache.SetValidUntilExpires(true);
Response.ContentType = "application/xml";
Response.Charset = "utf-8";
XmlTextWriter xml = new XmlTextWriter(Response.OutputStream, System.Text.Encoding.UTF8)
{
Formatting = Formatting.None
};
xml.WriteStartDocument();
xml.WriteStartElement("strings");
xml.WriteStartElement("string");
xml.WriteAttributeString("id", "lbl_login");
xml.WriteString(GetTranslation("label_login", strLang));
xml.WriteEndElement();
// ... the other labels
xml.WriteEndElement(); //</strings>
xml.Close();
}
Some other considerations:
set the the property AutoPostback of the dropdownlist to false.
Happens both for view-model properties named 'Password' and 'PIN'. You can bypass the behavior by defining those as:
string Password ;
... rather than:
string Password { get; set; }
If you do so, features such the 'LabelFor' macro displaying 'DisplayAttribute.Name' no longer works, so you'd have to define those directly in the HTML.
Or you can simply name the fields something other than 'Password' or 'PIN'.

Ajax control toolkit Rating Control- Override RatingBehavior.js

Rating control in Ajax control toolkit will not raise the event if a user clicks on current rating star because in behaviour js it has checked
if (this._ratingValue != this._currentRating), so I want to override this method without changing js and building toolkit. How can I do it, can I extend rating control and override RatingBehavior.js or any other solution.
_onStarClick : function(e) {
/// <summary>
/// Handler for a star's click event
/// </summary>
/// <param name="e" type="Sys.UI.DomEvent">
/// Event info
/// </param>
if (this._readOnly) {
return;
}
if (this._ratingValue != this._currentRating) {
this.set_Rating(this._currentRating);
}
},
I have solved it by putting the code just above
function AddNewRatingHandler() {
AjaxControlToolkit.RatingBehavior.prototype._onStarClick =
function(e) {
if (this._readOnly) {
return;
}
// if (this._ratingValue != this._currentRating) {
this.set_Rating(this._currentRating);
// }
};
AjaxControlToolkit.RatingBehavior.prototype.set_Rating = function(value) {
// if (this._ratingValue != value) {
this._ratingValue = value;
this._currentRating = value;
if (this.get_isInitialized()) {
if ((value < 0) || (value > this._maxRatingValue)) {
return;
}
this._update();
AjaxControlToolkit.RatingBehavior.callBaseMethod(this, 'set_ClientState', [this._ratingValue]);
this.raisePropertyChanged('Rating');
this.raiseRated(this._currentRating);
this._waitingMode(true);
var args = this._currentRating + ";" + this._tag;
var id = this._callbackID;
if (this._autoPostBack) {
__doPostBack(id, args);
}
else {
WebForm_DoCallback(id, args, this._receiveServerData, this, this._onError, true)
}
}
// }
};
}
AddNewRatingHandler();
</script>
</div>
</form>

Resources