]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | /**\r |
2 | * This example shows a common site registration form. The form appears to be simple but\r | |
3 | * it shows a few special things:\r | |
4 | *\r | |
5 | * - The display of field errors has been customized. Fields have `msgTarget: 'none'` so\r | |
6 | * the errors are not displayed with the individual fields; instead event listeners are\r | |
7 | * attached to the FormPanel to group up all error messages into a custom global error\r | |
8 | * indicator, with a persistent tooltip showing the error details.\r | |
9 | * - The "Terms of Use" link has an event handler attached so it opens the page in a modal\r | |
10 | * Ext.Window.\r | |
11 | * - The password fields have custom validation attached to verify the user enters the\r | |
12 | * same value in both.\r | |
13 | * - The submit button has `formBind: true` so it is only enabled when the form becomes\r | |
14 | * valid.\r | |
15 | */\r | |
16 | Ext.define('KitchenSink.view.form.CustomErrorHandling', {\r | |
17 | extend: 'Ext.form.Panel',\r | |
18 | xtype: 'form-customerrors',\r | |
19 | controller: 'form-customerrors',\r | |
20 | \r | |
21 | //<example>\r | |
22 | requires: [\r | |
23 | 'KitchenSink.view.form.CustomErrorHandlingController'\r | |
24 | ],\r | |
25 | \r | |
26 | exampleTitle: 'Custom Error Handling',\r | |
27 | otherContent: [{\r | |
28 | type: 'ViewController',\r | |
29 | path: 'classic/samples/view/form/CustomErrorHandlingController.js'\r | |
30 | }],\r | |
31 | //</example>\r | |
32 | \r | |
33 | frame: true,\r | |
34 | width: 350,\r | |
35 | bodyPadding: 10,\r | |
36 | bodyBorder: true,\r | |
37 | title: 'Account Registration',\r | |
38 | \r | |
39 | defaults: {\r | |
40 | anchor: '100%'\r | |
41 | },\r | |
42 | \r | |
43 | fieldDefaults: {\r | |
44 | labelWidth: 110,\r | |
45 | labelAlign: 'left',\r | |
46 | msgTarget: 'none',\r | |
47 | invalidCls: '' //unset the invalidCls so individual fields do not get styled as invalid\r | |
48 | },\r | |
49 | \r | |
50 | /*\r | |
51 | * Listen for validity change on the entire form and update the combined error icon\r | |
52 | */\r | |
53 | listeners: {\r | |
54 | validitychange: 'updateErrorState',\r | |
55 | errorchange: 'updateErrorState'\r | |
56 | },\r | |
57 | \r | |
58 | dockedItems: [{\r | |
59 | cls: Ext.baseCSSPrefix + 'dd-drop-ok',\r | |
60 | xtype: 'container',\r | |
61 | dock: 'bottom',\r | |
62 | layout: {\r | |
63 | type: 'hbox',\r | |
64 | align: 'middle'\r | |
65 | },\r | |
66 | padding: '10 10 5',\r | |
67 | \r | |
68 | items: [{\r | |
69 | xtype: 'component',\r | |
70 | reference: 'formErrorState',\r | |
71 | height: '100%',\r | |
72 | invalidCls: Ext.baseCSSPrefix + 'form-invalid-icon-default',\r | |
73 | validCls: Ext.baseCSSPrefix + 'dd-drop-icon',\r | |
74 | baseCls: 'form-error-state',\r | |
75 | flex: 1,\r | |
76 | validText: 'Form is valid',\r | |
77 | invalidText: 'Form has errors',\r | |
78 | \r | |
79 | tipTpl: [\r | |
80 | '<ul class="' + Ext.baseCSSPrefix + 'list-plain">',\r | |
81 | '<tpl for=".">',\r | |
82 | '<li><span class="field-name">{name}</span>: ',\r | |
83 | '<span class="error">{error}</span>',\r | |
84 | '</li>',\r | |
85 | '</tpl>',\r | |
86 | '</ul>'\r | |
87 | ],\r | |
88 | \r | |
89 | setErrors: function(errors) {\r | |
90 | var me = this,\r | |
91 | tpl = me.tipTpl,\r | |
92 | tip = me.tip;\r | |
93 | \r | |
94 | if (!me.tipTpl.isTemplate) {\r | |
95 | tpl = me.tipTpl = new Ext.XTemplate(tpl);\r | |
96 | }\r | |
97 | \r | |
98 | if (!tip) {\r | |
99 | tip = me.tip = Ext.widget('tooltip', {\r | |
100 | target: me.el,\r | |
101 | title: 'Error Details:',\r | |
102 | minWidth: 200,\r | |
103 | autoHide: false,\r | |
104 | anchor: 'top',\r | |
105 | mouseOffset: [-11, -2],\r | |
106 | closable: true,\r | |
107 | constrainPosition: false,\r | |
108 | cls: 'errors-tip'\r | |
109 | });\r | |
110 | }\r | |
111 | \r | |
112 | errors = Ext.Array.from(errors);\r | |
113 | \r | |
114 | // Update CSS class and tooltip content\r | |
115 | if (errors.length) {\r | |
116 | me.addCls(me.invalidCls);\r | |
117 | me.removeCls(me.validCls);\r | |
118 | me.update(me.invalidText);\r | |
119 | tip.setDisabled(false);\r | |
120 | tip.update(tpl.apply(errors));\r | |
121 | tip.show();\r | |
122 | }\r | |
123 | else {\r | |
124 | me.addCls(me.validCls);\r | |
125 | me.removeCls(me.invalidCls);\r | |
126 | me.update(me.validText);\r | |
127 | tip.setDisabled(true);\r | |
128 | tip.hide();\r | |
129 | }\r | |
130 | }\r | |
131 | }, {\r | |
132 | xtype: 'button',\r | |
133 | formBind: true,\r | |
134 | disabled: true,\r | |
135 | text: 'Submit Registration',\r | |
136 | minWidth: 140,\r | |
137 | listeners: {\r | |
138 | click: 'submitRegistration'\r | |
139 | }\r | |
140 | }]\r | |
141 | }],\r | |
142 | \r | |
143 | items: [{\r | |
144 | xtype: 'textfield',\r | |
145 | name: 'username',\r | |
146 | fieldLabel: 'User Name',\r | |
147 | allowBlank: false,\r | |
148 | minLength: 6\r | |
149 | }, {\r | |
150 | xtype: 'textfield',\r | |
151 | name: 'email',\r | |
152 | fieldLabel: 'Email Address',\r | |
153 | vtype: 'email',\r | |
154 | allowBlank: false\r | |
155 | }, {\r | |
156 | xtype: 'textfield',\r | |
157 | name: 'password1',\r | |
158 | fieldLabel: 'Password',\r | |
159 | inputType: 'password',\r | |
160 | style: 'margin-top: 15px',\r | |
161 | allowBlank: false,\r | |
162 | minLength: 8\r | |
163 | }, {\r | |
164 | xtype: 'textfield',\r | |
165 | name: 'password2',\r | |
166 | fieldLabel: 'Repeat Password',\r | |
167 | inputType: 'password',\r | |
168 | allowBlank: false,\r | |
169 | \r | |
170 | /*\r | |
171 | * Custom validator implementation - checks that the value matches what was entered into\r | |
172 | * the password1 field.\r | |
173 | */\r | |
174 | validator: function(value) {\r | |
175 | var password1 = this.previousSibling('[name=password1]');\r | |
176 | return (value === password1.getValue()) ? true : 'Passwords do not match.'\r | |
177 | }\r | |
178 | },\r | |
179 | \r | |
180 | /*\r | |
181 | * Terms of Use acceptance checkbox. Two things are special about this:\r | |
182 | * 1) The boxLabel contains a HTML link to the Terms of Use page; a special\r | |
183 | * click listener opens this page in a modal Ext window for convenient viewing,\r | |
184 | * and the Decline and Accept buttons in the window update the checkbox's state\r | |
185 | * automatically.\r | |
186 | * 2) This checkbox is required, i.e. the form will not be able to be submitted\r | |
187 | * unless the user has checked the box. Ext does not have this type of validation\r | |
188 | * built in for checkboxes, so we add a custom getErrors method implementation.\r | |
189 | */\r | |
190 | {\r | |
191 | xtype: 'checkboxfield',\r | |
192 | name: 'acceptTerms',\r | |
193 | reference: 'acceptTerms',\r | |
194 | fieldLabel: 'Terms of Use',\r | |
195 | hideLabel: true,\r | |
196 | margin: '15 0 0 0',\r | |
197 | boxLabel: 'I have read and accept the <a href="#" class="terms">Terms of Use</a>.',\r | |
198 | \r | |
199 | // Listener to open the Terms of Use page link in a modal window\r | |
200 | // Note that the listener method itself is defined in the ViewController\r | |
201 | listeners: {\r | |
202 | click: {\r | |
203 | element: 'boxLabelEl',\r | |
204 | fn: 'onTermsOfUseElementClick'\r | |
205 | }\r | |
206 | },\r | |
207 | \r | |
208 | // Custom validation logic - requires the checkbox to be checked\r | |
209 | getErrors: function() {\r | |
210 | return this.getValue() ? [] : ['You must accept the Terms of Use']\r | |
211 | }\r | |
212 | }, {\r | |
213 | // The window is added to the form's children array to be handled\r | |
214 | // by the form's ViewController. In a more complicated case we would\r | |
215 | // probably want the window to have its own ViewController.\r | |
216 | xtype: 'window',\r | |
217 | reference: 'termsOfUseWindow',\r | |
218 | closeAction: 'hide',\r | |
219 | title: 'Terms of Use',\r | |
220 | modal: true,\r | |
221 | width: 700,\r | |
222 | height: 400,\r | |
223 | bodyPadding: '10 20',\r | |
224 | scrollable: true,\r | |
225 | \r | |
226 | // Wall of text\r | |
227 | loader: {\r | |
228 | url: 'data/form/terms-of-use.html',\r | |
229 | autoLoad: true\r | |
230 | },\r | |
231 | \r | |
232 | buttons: [{\r | |
233 | text: 'Decline',\r | |
234 | handler: 'declineTermsOfUse'\r | |
235 | }, {\r | |
236 | text: 'Accept',\r | |
237 | handler: 'acceptTermsOfUse'\r | |
238 | }]\r | |
239 | }],\r | |
240 | \r | |
241 | beforeDestroy: function() {\r | |
242 | var error = this.lookupReference('formErrorState');\r | |
243 | if (error) {\r | |
244 | Ext.destroy(error.tip);\r | |
245 | }\r | |
246 | this.callParent();\r | |
247 | }\r | |
248 | });\r |