I’ve been struggling with the best way to do this. What I have come up with first is on the fields lost focus event if the data isn’t valid I change the style of the control to have a Red bg. I then allow them to go to the next field.
The form is in a container, the Next button is on the form. It calls code that makes that one invisible and the next one visible. The problem is I can’t figure a way to check if any of the fields are in an error state.
The other issue is to stop the user from just hitting the next button without filling in all the fields.
One way would be to refactor the validity checks away from the LostFocus events and into methods that are called by the events. Then when the user clicks Next you could double check each field and refuse to move next if one or more are invalid.
I did it by creating subclasses of the standard controls and adding properties and methods to keep track of the “required” and “validation” statuses. When data in a control is changed it automatically updates these statuses and sets whatever indicator. Then in the ok/save/next button action, loop through the controls (or a separate array) to check the statuses.
Right now your validation code is in the LostFocus event for each field, right? Move that code to a method that checks the whole form (i.e. ValidateForm), or to methods that check each field (i.e. ValidateFirstName, ValidateEmail). Now you can call the validation code from the LostFocus event (Self.ValidateWhatever) and the click on the Next button.
Subclassing controls to track validation and/or support a set of validator methods/classes, as Jay suggests, can be even more useful and flexible.
Where exactly does the validation happen, on the server or on the browser? If it happens on the server, then it could generate a lot of post back traffic and slowness, no?
The validation happens on the server. All the client events send back information to server, so it is not good practice to validate on each lostfocus event. When you press the button “Save”, then validate all the fields and change the style of the specific webfield, which it does n’t follow your validation.
Xojo is nice in that you can validate on the LostFocus event if the field. So the user gets instant feedback. Not at the time the submit is hit. It is very fast (instant by the user, unless he is using some sort of morse code uplink from the jungle…)
If me.text ="" then
me.style=MyInvalidStyle
else
me.style=MyValidStyle
end if
I tried to loop through the controls but couldn’t get it to work. I finally gave up and checked each one. What did I do wrong.
I have about 10 fields i have to validate. All the controls Text fields and a couple of popmenus, they are all on a container control. The submit button is on the Window.
Lets day the window is called Win1 and the container is called Con1
This code is in a method of the container. Heres the code:
Dim lnX, lnCount As Integer
Dim IbValid, lbReturnVal as Boolean
for lnX = 0 to lnCount
IbValid = False
if Con1.ControlAtIndex(lnX) IsA WebTextField then
If DidValidate(Con1.ControlAtIndex(lnX).Name) then IbValid = True
end
if Con1.Control(lnX) IsA CheckBox then
CheckBox(Con1.Control(lnX)).Value = False
end
if Con1.Control(lnX) IsA ComboBox then
ComboBox(Con1.Control(lnX)).ListIndex = -1
end
if Con1.ControlAtIndex(lnX) IsA WebPopupMenu then
if WebPopupMenu(Con1.ControlAtIndex(lnX)).ListIndex > -1 then IbValid = True
end
if not lbReturnVal then
lbReturnVal = IbValid
end
next
Return lbReturnVal
Where do you set lnCount? It should = Con1.ControlCount-1 (index is 0-based).
It looks to me like your “if not lbReturnVal then” section will cause the code to return True if any single control is valid. I would think that you would want to return True only if all of them are valid. So…
Dim lbReturnVal as Boolean = True
...
lbReturnVal = lbReturnVal And IbValid 'If IbValid is ever false, lbReturnVal will end up false.
This would require making IbValid = True at the top of each loop because you’re using the loop to do other stuff so some iterations may never have a validity check.
I have this Method that checks all controls that need validation. This is called from the lost focus like this:
Call DidValidate(Me)
Here is that method:
'Returns true if field validated
Dim ln as Integer
Dim lbValid as boolean
lbValid = True
lbObj.Style = EntryFields
if lbObj.Name = "txtSecondaryEmail" and lbObj.Text = "" then
Return lbValid
end
if lbObj.Name = "txtPhoneHome" or lbObj.Name = "txtPhoneCell" then
if lbObj.Text = "" then
txtPhoneHome.Style = EntryFieldsError
txtPhoneCell.Style = EntryFieldsError
lbValid = False
else
txtPhoneHome.Style = EntryFields
txtPhoneCell.Style = EntryFields
end
end
if lbObj.Text = "" then
lbObj.Style = EntryFieldsError
Return False
end
Select Case lbObj.Name
case "txtFirst"
if lbObj.Text <> "" then
txtNick.text = lbObj.Text
end
Case "txtBDayDay"
ln = lbObj.Text.Val
If ln < 1 or ln > 31 then
lbObj.Style = EntryFieldsError
lbValid = False
end
Case "txtBDayMonth"
ln = lbObj.Text.Val
If ln < 1 or ln > 12 then
lbObj.Style = EntryFieldsError
lbValid = False
end
Case "txtBDayYear"
ln = lbObj.Text.Val
If ln < 1914 or ln > 2114 then
lbObj.Style = EntryFieldsError
lbValid = False
Exit
end
Dim ldBDay As Date
Dim sDate as String
sDate = txtBDayMonth.text + "/" + txtBDayDay.Text + "/" + txtBDayYear.Text
If not validDate(sDate, ldBDay) then
lbObj.Style = EntryFieldsError
lbValid = False
end
Case "txtPrimaryEmail", "txtSecondaryEmail"
If not ValidateEmail(lbObj.Text) then
lbObj.Style = EntryFieldsError
lbValid = False
end
end
Return lbValid
This code works to validate the individual controls, where the problem is if the user skips over controls and tries to go to the next container, Making this container invisible and making next one visible. I try and loop through the controls to check the entire form.
Here is the looping code, getting compiler error (Parameter not compatible with this function) on
IbValid = DidValidate(WebContainer.ControlAtIndex(lnX).Name)
'loop through controls to validate each one.
Dim lnX, lnCount As Integer
Dim IbValid, lbReturnVal as Boolean
lbReturnVal = True
lnCount = Self.ControlCount - 1
for lnX = 0 to lnCount
IbValid = False
if WebContainer.ControlAtIndex(lnX) IsA WebTextField then
IbValid = DidValidate(WebContainer.ControlAtIndex(lnX).Name) '<<<-----Parameter not compatible with this function
end
if WebContainer.ControlAtIndex(lnX) IsA WebPopupMenu then
IbValid = (WebPopupMenu(WebContainer.ControlAtIndex(lnX)).ListIndex > -)
end
if not IbValid then
lbReturnVal = IbValid
end
next
Return lbReturnVal
In LostFocus you call DidValidate(Me). You pass the control instance. But in the loop you’re passing the control name, a string. I don’t see the actual declaration for DidValidate, but I’m guessing lbObj is the input parameter, i.e. you need to pass the object cast to the correct type:
if WebContainer.ControlAtIndex(lnX) IsA WebTextField then
Dim objTextField As WebTextField
objTextField = WebTextField( WebContainer.ControlAtIndex(lnX) ) 'This is how you cast types in Xojo.
IbValid = DidValidate(objTextField)
Also, you forgot the -1 in this line, there’s just a -:
FYI - you may want to consider an OOP solution to validation in the future. Having a long method where you work off the control name becomes difficult to modify and maintain, though for right now it can certainly work.