11const Validator = require ( "validatorjs" ) ;
2- import { IReactComponent , IOptions , IInputErrors } from "./types/ReactTypes " ;
2+ import { IReactComponent , IOptions , IValidatorErrors , IDynamicKeyValues , ReactFormSubmitEventHandler } from "./specs/react-form-input-validator.spec " ;
33
4- class ReactFormValidator {
4+ class ReactFormValidator extends EventTarget {
55 private component : IReactComponent ;
6- private submitCallback : Function ;
76 private rules : object = { } ;
8- private errors : IInputErrors = { } ;
7+ private errors : IValidatorErrors = { } ;
8+
9+ /**
10+ * Event registered to notify the onreactformsubmit in {@link ReactFormValidator}.
11+ * @returns A callback function {@link ReactFormSubmitEventHandler}.
12+ * @example
13+ * ```js
14+ *
15+ * // Refer "ReactFormValidator Interface" for react input form validator object creation
16+ *
17+ * this.form.addEventListener("onreactformsubmit", (fields) => {
18+ * // Make your ajax calls here.
19+ * });
20+ * // or
21+ * this.form.onreactformsubmit = (fields) => {
22+ * // Make your ajax calls here.
23+ * }
24+ * ```
25+ */
26+ public onreactformsubmit : ReactFormSubmitEventHandler ;
927
1028 /**
1129 * Construct the React Input Form Validator instance.
@@ -17,23 +35,18 @@ class ReactFormValidator {
1735 * @param options {@link Options }
1836 * @example
1937 * ```js
20- * let form = new ReactFormValidator(this,
21- * {
22- * email: "required|email",
23- * },
24- * (fields) => {
25- * // Make ajax request here to send data to server
26- * },
38+ *
39+ * // Recommanded to do this in constructor of the form component.
40+ * this.form = new ReactFormValidator(this,
2741 * {
2842 * locale: 'en'
2943 * })
3044 * ```
3145 */
32- constructor ( component : IReactComponent , rules : object , callback : Function , options ?: IOptions ) {
46+ constructor ( component : IReactComponent , options ?: IOptions ) {
47+ super ( ) ;
3348 ReactFormValidator . useLang ( ( options && options . locale ) ? options . locale : "en" ) ;
3449 this . component = component ;
35- this . rules = rules ;
36- this . submitCallback = callback ;
3750 this . handleFieldsChange = this . handleFieldsChange . bind ( this ) ;
3851 this . handleSubmit = this . handleSubmit . bind ( this ) ;
3952 this . handleBlurEvent = this . handleBlurEvent . bind ( this ) ;
@@ -45,9 +58,8 @@ class ReactFormValidator {
4558 * @param locale string
4659 * @example
4760 * ```js
48- * import ReactInputFormValidator from "react-input-form-validator";
4961 *
50- * ReactInputFormValidator .useLang("en");
62+ * ReactFormValidator .useLang("en");
5163 * ```
5264 */
5365 static useLang ( locale : string ) : void {
@@ -63,7 +75,8 @@ class ReactFormValidator {
6375 * :attribute inside errorMessage will be replaced with the attribute name.
6476 * @example
6577 * ```js
66- * ReactInputFormValidator.register('telephone', function(value, requirement, attribute) {
78+ *
79+ * ReactFormValidator.register('telephone', function(value, requirement, attribute) {
6780 * return value.match(/^\d{3}-\d{3}-\d{4}$/);
6881 * }, 'The :attribute phone number is not in the format XXX-XXX-XXXX.');
6982 *
@@ -73,6 +86,26 @@ class ReactFormValidator {
7386 Validator . register ( name , callbackFn , errorMessage ) ;
7487 }
7588
89+ /**
90+ * Register an asynchronous rule which accepts a passes callback
91+ *
92+ * @param name The name of the rule.
93+ * @param callbackFn
94+ * @example
95+ * ```js
96+ *
97+ * Validator.registerAsync('username_available', function(username, attribute, req, passes) {
98+ * // do your database/api checks here etc
99+ * // then call the `passes` method where appropriate:
100+ * passes(); // if username is available
101+ * passes(false, 'Username has already been taken.'); // if username is not available
102+ * });
103+ * ```
104+ */
105+ static registerAsync ( name : string , callbackFn : Function ) : void {
106+ Validator . registerAsync ( name , callbackFn ) ;
107+ }
108+
76109 /**
77110 * You can also add your own custom language by calling setMessages:
78111 *
@@ -81,7 +114,7 @@ class ReactFormValidator {
81114 * @example
82115 * ```js
83116 *
84- * ReactInputFormValidator .setMessages('lang_code', {
117+ * ReactFormValidator .setMessages('lang_code', {
85118 * required: 'The :attribute field is required.'
86119 * });
87120 * ```
@@ -95,7 +128,8 @@ class ReactFormValidator {
95128 * @param name The name of the rule
96129 * @example
97130 * ```js
98- * ReactInputFormValidator.getMessages('lang_code');
131+ *
132+ * ReactFormValidator.getMessages('lang_code');
99133 * ```
100134 */
101135 static getMessages ( name : string ) : object {
@@ -106,31 +140,65 @@ class ReactFormValidator {
106140 * Get the default language being used
107141 * @example
108142 * ```js
109- * ReactInputFormValidator.getDefaultLang(); // returns e.g. 'en'
143+ *
144+ * ReactFormValidator.getDefaultLang(); // returns e.g. 'en'
110145 * ```
111146 */
112147 static getDefaultLang ( ) : string {
113148 return Validator . getDefaultLang ( ) ;
114149 }
115150
151+ /**
152+ * You can supply global custom attribute names in your app with the attributes property.
153+ *
154+ * @param callbackFn A Callback function to configure the attribute name.
155+ * @example
156+ * ```js
157+ *
158+ * ReactFormValidator.setAttributeFormatter(function(attribute) {
159+ * return attribute.replace(/_/g, ' ');
160+ * });
161+ * ```
162+ */
163+ static setAttributeFormatter ( callbackFn : Function ) : void {
164+ Validator . setAttributeFormatter ( callbackFn ) ;
165+ }
166+
167+ /**
168+ * Set the validation rules for form fields.
169+ * @param rules The rules to validate.
170+ * @example
171+ * ```js
172+ *
173+ * this.form.useRules({
174+ * email: "required|email",
175+ * password: "required"
176+ * })
177+ * ```
178+ */
179+ public useRules ( rules ) : void {
180+ this . rules = rules ;
181+ }
182+
116183 /**
117184 * Handle onchange event for input fields.
118185 *
119186 * @example
120187 * ```js
188+ *
121189 * // Refer "ReactFormValidator Interface" for react input form validator object creation
122190 *
123- * <input name="email" onChange={form.handleFieldsChange} value={this.state.fields.email}>
191+ * <input name="email" onChange={this. form.handleFieldsChange} value={this.state.fields.email}>
124192 * ```
125193 */
126194 public handleFieldsChange ( event ) {
127- const name = event . target . name ;
195+ const name : string = event . target . name ;
128196 if ( this . component && name ) {
129197 const fields = Object . assign ( { } , this . component . state . fields ) ;
130198 fields [ name ] = ( event . target . type === "checkbox" ) ? this . getCheckboxValues ( event . target ) :
131199 ( event . target . type === "radio" ) ? this . getRadioButtonValues ( event . target ) :
132200 event . target . value ;
133- this . component . setState ( { fields : fields , isOwnUpdate : true } ) ;
201+ this . component . setState ( { fields : fields , isValidatorUpdate : true } ) ;
134202 }
135203 }
136204
@@ -140,6 +208,7 @@ class ReactFormValidator {
140208 * @param event onsubmit event
141209 * @example
142210 * ```js
211+ *
143212 * // Refer "ReactFormValidator Interface" for react input form validator object creation
144213 *
145214 * <form onSubmit={form.handleSubmit}>
@@ -148,8 +217,8 @@ class ReactFormValidator {
148217 */
149218 public handleSubmit ( event ) {
150219 event . preventDefault ( ) ;
151- if ( this . validateForm ( event . target ) && this . submitCallback ) {
152- this . submitCallback ( this . component . state . fields ) ;
220+ if ( this . validateForm ( event . target ) ) {
221+ this . dispatchEvent ( this . getEvent ( this . component . state . fields ) ) ;
153222 }
154223 }
155224
@@ -159,6 +228,7 @@ class ReactFormValidator {
159228 * @param event onblur event
160229 * @example
161230 * ```js
231+ *
162232 * // Refer "ReactFormValidator Interface" for react input form validator object creation
163233 * <input
164234 * name="email"
@@ -170,21 +240,11 @@ class ReactFormValidator {
170240 */
171241 public handleBlurEvent ( event ) {
172242 const element : HTMLInputElement = event . target ;
173-
174- const initValidator = ( ) => {
175- const inputErrors = this . validate ( element ) ;
176- if ( this . component && this . component . hasOwnProperty ( "state" ) ) {
177- this . errors = this . getErrorMessage ( inputErrors as Array < any > ,
178- this . component . state . inputErrors ? this . component . state . inputErrors : { } ) ;
179- this . component . setState ( { inputErrors : this . errors } ) ;
180- }
181- } ;
182-
183- if ( ( element . dataset !== undefined && element . dataset . hasOwnProperty ( "datepicker" ) ) ||
184- element . getAttribute ( "data-datepicker" ) ) {
185- setTimeout ( initValidator , 200 ) ;
186- } else {
187- initValidator ( ) ;
243+ const inputErrors = this . validate ( element ) ;
244+ if ( this . component && this . component . hasOwnProperty ( "state" ) ) {
245+ this . errors = this . getErrorMessage ( inputErrors as Array < any > ,
246+ this . component . state . errors ? this . component . state . errors : { } ) ;
247+ this . component . setState ( { errors : this . errors , isValidatorUpdate : true } ) ;
188248 }
189249 }
190250
@@ -196,27 +256,28 @@ class ReactFormValidator {
196256 private validateForm ( form ) : boolean {
197257 if ( ! this . component || ! this . component . state ) {
198258 this . component . state = {
199- inputErrors : { }
259+ errors : { }
200260 } ;
201261 }
202262
203- form . querySelectorAll ( "textarea,select,input:not([type='submit']):not([type='file']):not([data-ignore-validation]):not([data-unique]) " )
263+ form . querySelectorAll ( "textarea,select,input:not([type='submit']):not([type='file']):not([data-ignore-validation])" )
204264 . forEach ( ( elem ) => {
205265 const inputErrors = this . validate ( elem ) ;
206- Object . assign ( this . component . state . inputErrors ,
207- this . getErrorMessage ( inputErrors as Array < any > , this . component . state . inputErrors ) ) ;
266+ Object . assign ( this . component . state . errors ,
267+ this . getErrorMessage ( inputErrors as Array < any > , this . component . state . errors ) ) ;
208268 } ) ;
209269
210270 this . component . setState ( {
211- inputErrors : this . component . state . inputErrors
271+ errors : this . component . state . errors ,
272+ isValidatorUpdate : true
212273 } ) ;
213274
214- if ( Object . keys ( this . component . state . inputErrors ) [ 0 ] &&
215- form . querySelector ( `[name="${ Object . keys ( this . component . state . inputErrors ) [ 0 ] } "]` ) ) {
216- form . querySelector ( `[name="${ Object . keys ( this . component . state . inputErrors ) [ 0 ] } "]` ) . focus ( ) ;
275+ if ( Object . keys ( this . component . state . errors ) [ 0 ] &&
276+ form . querySelector ( `[name="${ Object . keys ( this . component . state . errors ) [ 0 ] } "]` ) ) {
277+ form . querySelector ( `[name="${ Object . keys ( this . component . state . errors ) [ 0 ] } "]` ) . focus ( ) ;
217278 }
218279
219- return Object . keys ( this . component . state . inputErrors ) . length === 0 ;
280+ return Object . keys ( this . component . state . errors ) . length === 0 ;
220281 }
221282
222283 /**
@@ -272,7 +333,7 @@ class ReactFormValidator {
272333 *
273334 * @param element HTMLInputElement
274335 */
275- private validate ( element : HTMLInputElement ) : object {
336+ private validate ( element : HTMLInputElement ) : IDynamicKeyValues {
276337 const errors = { } ;
277338 const name = element . getAttribute ( "name" ) ;
278339 const data = {
@@ -294,22 +355,37 @@ class ReactFormValidator {
294355 } ) ;
295356 }
296357
358+ if ( element . hasAttribute ( "data-async" ) ) {
359+ const passes : Function = ( ) => {
360+ delete this . errors [ name ] ;
361+ } ;
362+
363+ const fails : Function = ( ) => {
364+ const errMessage : string = validate . errors . first ( name ) ;
365+ errors [ name ] = errMessage ;
366+ } ;
367+
368+ validate . checkAsync ( passes , fails ) ;
369+ return errors ;
370+ }
371+
297372 if ( validate . fails ( ) ) {
298373 const errMessage : string = validate . errors . first ( name ) ;
299374 errors [ name ] = errMessage ;
300375 return errors ;
301376 }
377+
302378 delete this . errors [ name ] ;
303379 return errors ;
304380 }
305381
306382 /**
307- * Format the error message from validatorjs error to {@link IInputErrors }
383+ * Format the error message from validatorjs error to {@link IValidatorErrors }
308384 *
309385 * @param inputErrors Array of error strings
310386 * @param errors Errors
311387 */
312- private getErrorMessage ( inputErrors : Array < any > , errors : any ) : IInputErrors {
388+ private getErrorMessage ( inputErrors : Array < any > , errors : IValidatorErrors ) : IValidatorErrors {
313389 Object . keys ( inputErrors ) . forEach ( ( field ) => {
314390 const msg = inputErrors [ field ] ;
315391 if ( msg ) {
@@ -321,6 +397,17 @@ class ReactFormValidator {
321397 } ) ;
322398 return errors ;
323399 }
400+
401+ /**
402+ * Creating custom event to send form data.
403+ *
404+ * @param details The form fields to send in the event
405+ */
406+ private getEvent ( details : any ) : CustomEvent {
407+ return new CustomEvent ( "onreactformsubmit" , {
408+ detail : details
409+ } ) ;
410+ }
324411}
325412
326413export default ReactFormValidator ;
0 commit comments