PDA

View Full Version : [CLOSED] Registration Form updateErrorState modifications



cwolcott
Apr 30, 2013, 3:26 PM
I used the Registration Form (http://examples2.ext.net/#/Form/Miscellaneous/Registration_Form/) example as the basis for this question. I have a user entry form with many more fields. Some fields are explicity hidden based on other field selections, other fields are items of a container that may be hidden.

Modifications to example

Removed "Terms of Use" to reduce code size;
Added Fillout defaults button for client and server side;
Added hidden TextField for testing;
Added hidden container with textfield for testing;
Added fieldset that is collapsible with textfield for testing;


1) I have created a Fillout Defaults button that will auto populate my fields for me on the client and server side. Before filling out the fields I suspend the Form events, thus the FieldValidityChange and FieldErrorChange are not fired. After I am done I resume the Form events and then manually fire the FieldValidityChange.

What is the proper way to fire the event on the server side?
Does this seem reasonable?


2) In the updateErrorState code I only process fields that are visible, thus;

Controls that are explicitly hidden are not processed.
Controls that are a child of a container that is hidden are not processed.

Controls that are a child of a collapsed fieldset are still processed.

Does this seem reasonable?



<%@ Page Language="C#" %>

<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
<script runat="server">

protected void FilloutDefaults(object sender, DirectEventArgs e)
{
FormPanel1.SuspendEvents();
txtUsername.SetValue("JanetJones");
txtEmail.SetValue("jjones@server.com");
FormPanel1.ResumeEvents();

//What is the correct way to fire the event from the server side?
//FormPanel1.FireEvent("FieldValidityChange", new object());
}

</script>
<!DOCTYPE html>
<html>
<head id="Head1" runat="server">
<title>Registration Form - Ext.NET Examples</title>
<link href="/resources/css/examples.css" rel="stylesheet" />
<style>
/* Styling of global error indicator */
.form-error-state
{
font-size: 11px;
padding-left: 20px;
height: 16px;
line-height: 18px;
background: no-repeat 0 0;
cursor: default;
}
.form-error-state-invalid
{
color: #C30;
background-image: url(../../../../icons/exclamation-png/ext.axd);
}
.form-error-state-valid
{
color: #090;
background-image: url(../../../../icons/accept-png/ext.axd);
}

/* Error details tooltip */
.errors-tip .error
{
font-style: italic;
}
</style>
<script>
function updateErrorState(form) {
var me = form,
errorCmp, fields, errors;

if (me.hasBeenDirty || me.getForm().isDirty()) { //prevents showing global error when form first loads
errorCmp = me.down('#formErrorState');
fields = me.getForm().getFields();
errors = [];
fields.each(function (field) {
// Only process fields that are visible, thus;
// Fields that are explicitly hidden are not processed.
// Fields that are a child of a container that is hidden are not processed.
// Fields that are a child of a FieldSet that is collapsed is processed.
if (field.isVisible(true)) {
Ext.Array.forEach(field.getErrors(), function (error) {
errors.push({ name: field.getFieldLabel(), error: error });
});
}
});
setErrors(errorCmp, errors);
me.hasBeenDirty = true;
}
}

function setErrors(cmp, errors) {
var me = cmp,
baseCls = me.baseCls,
tip = me.tooltips[0];

errors = Ext.Array.from(errors);

// Update CSS class and tooltip content
if (errors.length) {
me.addCls(baseCls + '-invalid');
me.removeCls(baseCls + '-valid');
me.update("Form has errors");
tip.setDisabled(false);
if (!tip.rendered) {
tip.show();
}
tip.update(me.bin[0].apply(errors));
} else {
me.addCls(baseCls + '-valid');
me.removeCls(baseCls + '-invalid');
me.update("Form is valid");
tip.setDisabled(true);
tip.hide();
}
}

function filloutDefaults() {
App.FormPanel1.suspendEvents();
App.txtUsername.setValue("BobSmith");
App.txtEmail.setValue("bsmith@client.com");
App.FormPanel1.resumeEvents();
App.FormPanel1.fireEvent('fieldvaliditychange', App.FormPanel1, null, null);
}
</script>
</head>
<body>
<form id="Form1" runat="server">
<ext:ResourceManager ID="ResourceManager1" runat="server" />
<h1>Registration Form</h1>
<br />
<ext:FormPanel ID="FormPanel1" runat="server" Frame="true" Width="450" BodyPadding="10"
BodyBorder="1" Title="Account Registration" DefaultAnchor="100%">
<FieldDefaults LabelAlign="Left" MsgTarget="None" InvalidCls="" />
<Listeners>
<FieldValidityChange Fn="updateErrorState" />
<FieldErrorChange Fn="updateErrorState" />
</Listeners>
<Items>
<ext:TextField ID="txtUsername" runat="server" Name="username" FieldLabel="User Name"
AllowBlank="false" MinLength="6" />
<ext:TextField ID="txtEmail" runat="server" Name="email" FieldLabel="Email Address"
AllowBlank="false" Vtype="email" />
<ext:TextField ID="txtPassword" runat="server" Name="password1" FieldLabel="Password"
InputType="Password" StyleSpec="margin-top:15px;" AllowBlank="false" MinLength="8" />
<ext:TextField ID="txtPasswordConfirm" runat="server" Name="password2" FieldLabel="Repeat Password"
InputType="Password" AllowBlank="false">
<Validator Handler="return (value === this.previousSibling('[name=password1]').getValue()) ? true : 'Passwords do not match.';" />
</ext:TextField>
<ext:TextField ID="txtJunk01" runat="server" FieldLabel="Junk (txt)" AllowBlank="false" Hidden="true" />
<ext:Container ID="cntrJunk01" runat="server" Hidden="false">
<Items>
<ext:TextField ID="txtJunk02" runat="server" FieldLabel="Junk (in Cntr)" AllowBlank="false" />
</Items>
</ext:Container>
<ext:FieldSet ID="fsJunk01" runat="server" Title="FieldSet" Collapsible="true" >
<Items>
<ext:TextField ID="txtJunk03" runat="server" FieldLabel="Junk (in FS)" AllowBlank="false" />
</Items>
</ext:FieldSet>
</Items>
<DockedItems>
<ext:Container ID="Container1" runat="server" Dock="Bottom" PaddingSpec="10 10 5">
<LayoutConfig>
<ext:HBoxLayoutConfig Align="Middle" />
</LayoutConfig>
<Items>
<ext:Component ID="Component1" runat="server" ItemID="formErrorState" BaseCls="form-error-state"
Flex="1">
<Bin>
<ext:XTemplate ID="XTemplate1" runat="server">
<Html>
<ul>
<tpl for=".">
<li>
<span class="field-name">{name}</span> : <span class="error">{error}</span>
</li>
</tpl>
</ul>
</Html>
</ext:XTemplate>
</Bin>
<ToolTips>
<ext:ToolTip ID="ToolTip1" runat="server" Title="Error Details:" AutoHide="false"
Anchor="top" MinWidth="200" MouseOffset="-11,-2" Closable="true" Disabled="true"
ConstrainPosition="false" Cls="errors-tip" />
</ToolTips>
</ext:Component>
<ext:SplitButton runat="server" Text="Fillout Defaults" Icon="PencilAdd">
<Menu>
<ext:Menu runat="server">
<Items>
<ext:MenuItem runat="server" Text="Client" Icon="Computer" OnClientClick="filloutDefaults" />
<ext:MenuItem runat="server" Text="Server" Icon="Server">
<DirectEvents>
<Click OnEvent="FilloutDefaults" Success="App.FormPanel1.fireEvent('fieldvaliditychange', App.FormPanel1, null, null);" />
</DirectEvents>
</ext:MenuItem>
</Items>
</ext:Menu>
</Menu>
</ext:SplitButton>
<ext:ToolbarSpacer runat="server" />
<ext:Button ID="Button1" runat="server" FormBind="true" Disabled="true" Text="Submit Registration"
Width="140">
<Listeners>
<Click Handler="var form = this.up('form').getForm(); if (form.isValid()) {Ext.Msg.alert('Submitted Values', form.getValues(true));}" />
</Listeners>
</ext:Button>
</Items>
</ext:Container>
</DockedItems>
</ext:FormPanel>
</form>
</body>
</html>

cwolcott
Apr 30, 2013, 4:16 PM
With further testing I have some issues. An example of an issue is txtJunk01 control:



<ext:TextField ID="txtJunk01" runat="server" FieldLabel="Junk (txt)" AllowBlank="false" Hidden="true" />


The updateErrorState function doesn't process the control because it is not visible (Good), but the Submit button has FormBind="true" and since the txtJunk01 control has AllowBlank="false" it won't enable the button.

Is there any way for FormBind to ignore non visible controls?
Should I handle show/hide events for controls to set AllowBlank to true/false?

Any other thoughts?

Daniil
Apr 30, 2013, 6:04 PM
Hi Chris,



Is there any way for FormBind to ignore non visible controls?


The validation process does take the hidden fields into account. There is no a simple way to override it.



Should I handle show/hide events for controls to set AllowBlank to true/false?


You can disable the fields on its Hide event and enable on Show. The validation process ignores disabled fields.

cwolcott
Apr 30, 2013, 7:03 PM
Thanks Daniil,

Can you take a look at the code and see what I am missing. I added a checkbox to toggle a textfield. I then added show/hide events to enable/disable the textfield.

1) Select Fillout Defaults->Client;
2) Initially the box is not checked, thus the textfield is part of the validation and the submit button is still disabled because formbind="true".
3) Check the box; the form is now valid and the submit button is enabled.
4) Uncheck the box; the form is now invalid, but the submit button is not disable. WHY?

6129613061316132



<%@ Page Language="C#" %>

<%@ Register Assembly="Ext.Net" Namespace="Ext.Net" TagPrefix="ext" %>
<script runat="server">

protected void FilloutDefaults(object sender, DirectEventArgs e)
{
FormPanel1.SuspendEvents();
txtUsername.SetValue("JanetJones");
txtEmail.SetValue("jjones@server.com");
FormPanel1.ResumeEvents();

//What is the correct way to fire the event from the server side?
//FormPanel1.FireEvent("FieldValidityChange", new object());
}

</script>
<!DOCTYPE html>
<html>
<head id="Head1" runat="server">
<title>Registration Form - Ext.NET Examples</title>
<link href="/resources/css/examples.css" rel="stylesheet" />
<style>
/* Styling of global error indicator */
.form-error-state
{
font-size: 11px;
padding-left: 20px;
height: 16px;
line-height: 18px;
background: no-repeat 0 0;
cursor: default;
}
.form-error-state-invalid
{
color: #C30;
background-image: url(../../../../icons/exclamation-png/ext.axd);
}
.form-error-state-valid
{
color: #090;
background-image: url(../../../../icons/accept-png/ext.axd);
}

/* Error details tooltip */
.errors-tip .error
{
font-style: italic;
}
</style>
<script>
function updateErrorState(form) {
var me = form,
errorCmp, fields, errors;

if (me.hasBeenDirty || me.getForm().isDirty()) { //prevents showing global error when form first loads
errorCmp = me.down('#formErrorState');
fields = me.getForm().getFields();
errors = [];
fields.each(function (field) {
// Only process fields that are visible, thus;
// Fields that are explicitly hidden are not processed.
// Fields that are a child of a container that is hidden are not processed.
// Fields that are a child of a FieldSet that is collapsed is processed.
if (field.isVisible(true)) {
Ext.Array.forEach(field.getErrors(), function (error) {
errors.push({ name: field.getFieldLabel(), error: error });
});
}
});
setErrors(errorCmp, errors);
me.hasBeenDirty = true;
}
}

function setErrors(cmp, errors) {
var me = cmp,
baseCls = me.baseCls,
tip = me.tooltips[0];

errors = Ext.Array.from(errors);

// Update CSS class and tooltip content
if (errors.length) {
me.addCls(baseCls + '-invalid');
me.removeCls(baseCls + '-valid');
me.update("Form has errors");
tip.setDisabled(false);
if (!tip.rendered) {
tip.show();
}
tip.update(me.bin[0].apply(errors));
} else {
me.addCls(baseCls + '-valid');
me.removeCls(baseCls + '-invalid');
me.update("Form is valid");
tip.setDisabled(true);
tip.hide();
}
}

function filloutDefaults() {
App.FormPanel1.suspendEvents();
App.txtUsername.setValue("BobSmith");
App.txtEmail.setValue("bsmith@client.com");
App.txtPassword.setValue("aaaaaaaa");
App.txtPasswordConfirm.setValue("aaaaaaaa");
App.FormPanel1.resumeEvents();
App.FormPanel1.fireEvent('fieldvaliditychange', App.FormPanel1, null, null);
}
</script>
</head>
<body>
<form id="Form1" runat="server">
<ext:ResourceManager ID="ResourceManager1" runat="server" />
<h1>
Registration Form</h1>
<br />
<ext:FormPanel ID="FormPanel1" runat="server" Frame="true" Width="450" BodyPadding="10"
BodyBorder="1" Title="Account Registration" DefaultAnchor="100%">
<FieldDefaults LabelAlign="Left" MsgTarget="None" InvalidCls="" />
<Listeners>
<FieldValidityChange Fn="updateErrorState" />
<FieldErrorChange Fn="updateErrorState" />
</Listeners>
<Items>
<ext:TextField ID="txtUsername" runat="server" Name="username" FieldLabel="User Name"
AllowBlank="false" MinLength="6" />
<ext:TextField ID="txtEmail" runat="server" Name="email" FieldLabel="Email Address"
AllowBlank="false" Vtype="email" />
<ext:TextField ID="txtPassword" runat="server" Name="password1" FieldLabel="Password"
InputType="Password" StyleSpec="margin-top:15px;" AllowBlank="false" MinLength="8" />
<ext:TextField ID="txtPasswordConfirm" runat="server" Name="password2" FieldLabel="Repeat Password"
InputType="Password" AllowBlank="false">
<Validator Handler="return (value === this.previousSibling('[name=password1]').getValue()) ? true : 'Passwords do not match.';" />
</ext:TextField>
<ext:Checkbox runat="server" FieldLabel="Hide Field">
<Listeners>
<Change Handler="#{txtJunk01}.setVisible(!this.getValue())" />
</Listeners>
</ext:Checkbox>
<ext:TextField ID="txtJunk01" runat="server" FieldLabel="Junk (txt)" AllowBlank="false">
<Listeners>
<Show Handler="#{txtJunk01}.setDisabled(false); #{FormPanel1}.fireEvent('fieldvaliditychange',#{Fo rmPanel1},null,null);" />
<Hide Handler="#{txtJunk01}.setDisabled(true); #{FormPanel1}.fireEvent('fieldvaliditychange',#{Fo rmPanel1},null,null);" />
</Listeners>
</ext:TextField>
</Items>
<DockedItems>
<ext:Container ID="Container1" runat="server" Dock="Bottom" PaddingSpec="10 10 5">
<LayoutConfig>
<ext:HBoxLayoutConfig Align="Middle" />
</LayoutConfig>
<Items>
<ext:Component ID="Component1" runat="server" ItemID="formErrorState" BaseCls="form-error-state"
Flex="1">
<Bin>
<ext:XTemplate ID="XTemplate1" runat="server">
<Html>
<ul>
<tpl for=".">
<li>
<span class="field-name">{name}</span> : <span class="error">{error}</span>
</li>
</tpl>
</ul>
</Html>
</ext:XTemplate>
</Bin>
<ToolTips>
<ext:ToolTip runat="server" Title="Error Details:" AutoHide="false"
Anchor="top" MinWidth="200" MouseOffset="-11,-2" Closable="true" Disabled="true"
ConstrainPosition="false" Cls="errors-tip" />
</ToolTips>
</ext:Component>
<ext:SplitButton runat="server" Text="Fillout Defaults" Icon="PencilAdd">
<Menu>
<ext:Menu runat="server">
<Items>
<ext:MenuItem runat="server" Text="Client" Icon="Computer" OnClientClick="filloutDefaults" />
<ext:MenuItem runat="server" Text="Server" Icon="Server">
<DirectEvents>
<Click OnEvent="FilloutDefaults" Success="App.FormPanel1.fireEvent('fieldvaliditychange', App.FormPanel1, null, null);" />
</DirectEvents>
</ext:MenuItem>
</Items>
</ext:Menu>
</Menu>
</ext:SplitButton>
<ext:ToolbarSpacer runat="server" />
<ext:Button ID="Button1" runat="server" FormBind="true" Disabled="true" Text="Submit Registration"
Width="140">
<Listeners>
<Click Handler="var form = this.up('form').getForm(); if (form.isValid()) {Ext.Msg.alert('Submitted Values', form.getValues(true));}" />
</Listeners>
</ext:Button>
</Items>
</ext:Container>
</DockedItems>
</ext:FormPanel>
</form>
</body>
</html>

Daniil
May 01, 2013, 5:43 AM
Are you firing the "fieldvaliditychange" to trigger validation?

Please call a FormPanel's isValid method instead.

<Listeners>
<Show Handler="#{txtJunk01}.setDisabled(false); #{FormPanel1}.isValid();" />
<Hide Handler="#{txtJunk01}.setDisabled(true); #{FormPanel1}.isValid();" />
</Listeners>

Generally, I would avoid using the fireEvent method at all. If it needs to use, it probably means that something is wrong in the design.

Could you also clarify why you suspend events?

App.FormPanel1.suspendEvents();

To improve the performance? If so, I think it is better to use:

Ext.supendLayouts();
//a bunch of updates
Ext.resumeLayouts(true);

cwolcott
May 01, 2013, 4:28 PM
Thank you for the #{FormPanel1}.isValid() call, that works great.

I removed the Show/Hide listener from the textfield and just performed everything on the CheckBox Change listener. That all seems to work great.
So that takes care of the fields that change their visibilty state (thus needing to change their disabled state).



<ext:Checkbox runat="server" FieldLabel="Hide Field">
<Listeners>
<Change Handler="#{txtJunk01}.setVisible(!this.getValue()); #{txtJunk01}.setDisabled(this.getValue()); #{FormPanel1}.isValid()" />
</Listeners>
</ext:Checkbox>
<ext:TextField ID="txtJunk01" runat="server" FieldLabel="Junk (txt)" AllowBlank="false" />




Could you also clarify why you suspend events?


I was using the SuspendEvents and ResumeEvents function calls to wrapped around the assignment of control values (setValue). This increased performance since the FormPanel has FieldValidityChange and FieldErrorChange listeners. Everytime I called a controls setValue the listeners function would be called. I didn't need this to occur since I was providing valid values for each field in the Fillout Defaults button click.

Is that a valid reason to use SuspendEvents and ResumeEvents?

If I call SuspendLayouts and ResumeLayouts the FormPanel FieldValidityChange and FieldErrorChange listeners are called on each setValue call.

Daniil
May 01, 2013, 5:24 PM
Yes, I think you use the suspendEvents and resumeEvents methods correctly according your scenario.

Though you probably need to pass true to:

App.FormPanel1.suspendEvents(true);

It queues up suspended events to be fired after the resumeEvents call instead of discarding all suspended events.