@@ -174,10 +174,16 @@ define([
174174 requiredIcon ( fieldName ) {
175175 const fieldState = this . state . form . fields [ fieldName ] ;
176176 const classes = ( ( ) => {
177- if ( fieldState . status !== 'VALID' ) {
177+ switch ( fieldState . status ) {
178+ case 'VALID' :
179+ return 'glyphicon-ok text-success' ;
180+ case 'LOCAL_VALID' :
181+ return 'glyphicon-asterisk text-danger' ;
182+ case 'REMOTE_VALIDATING' :
183+ return 'glyphicon-asterisk text-danger' ;
184+ case 'INVALID' :
178185 return 'glyphicon-asterisk text-danger' ;
179186 }
180- return 'glyphicon-ok text-success' ;
181187 } ) ( ) ;
182188
183189 return html `
@@ -189,26 +195,17 @@ define([
189195
190196 getFieldBorderClass ( fieldName ) {
191197 const fieldState = this . state . form . fields [ fieldName ] ;
192- if ( fieldState . status !== 'VALID' ) {
198+ switch ( fieldState . status ) {
199+ case 'VALID' :
200+ return '' ;
201+ case 'LOCAL_VALID' :
202+ return 'has-error' ;
203+ case 'REMOTE_VALIDATING' :
204+ return 'has-error' ;
205+ case 'INVALID' :
193206 return 'has-error' ;
194207 }
195208 return '' ;
196-
197- // if (fieldState.isValidating) {
198- // // return '1px solid yellow';
199- // return 'bs-border-warning';
200- // }
201- // if (fieldState.isModified) {
202- // if (fieldState.status === 'VALID') {
203- // // return '1px solid transparent';
204- // return 'bs-border-invisible';
205- // }
206- // // return '1px solid red';
207- // return 'bs-border-danger';
208-
209- // }
210- // // return '1px solid transparent';
211- // return 'bs-border-invisible';
212209 }
213210
214211 renderFormRow ( field , info ) {
@@ -348,6 +345,9 @@ define([
348345 } ;
349346 }
350347 } ,
348+ ] ,
349+ remoteRuleMessage : 'Username is valid, check if available with KBase' ,
350+ remoteRules : [
351351 {
352352 validate : async ( value ) => {
353353 const auth2Client = new auth2 . Auth2 ( {
@@ -474,6 +474,42 @@ define([
474474 }
475475 }
476476
477+ if ( 'remoteRules' in fieldDefinition ) {
478+ return {
479+ value,
480+ isModified : true ,
481+ status : 'LOCAL_VALID' ,
482+ validationMessage : fieldDefinition . remoteRuleMessage
483+ } ;
484+ }
485+ return {
486+ value,
487+ isModified : true ,
488+ status : 'VALID'
489+ } ;
490+ }
491+
492+ async evaluateRemoteRules ( fieldName ) {
493+ if ( ! ( 'remoteRules' in fieldDefinition ) ) {
494+ return ;
495+ }
496+ // Apply remote rules.
497+ const fieldState = this . getFieldState ( fieldName ) ;
498+ const fieldDefinition = this . getFieldDefinition ( fieldName ) ;
499+
500+ const value = fieldState . value ;
501+
502+ for ( const rule of fieldDefinition . remoteRules ) {
503+ const { isValid, message} = await rule . validate ( value ) ;
504+ if ( ! isValid ) {
505+ return {
506+ value,
507+ isModified : true ,
508+ status : 'INVALID' ,
509+ validationMessage : message
510+ } ;
511+ }
512+ }
477513 return {
478514 value,
479515 isModified : true ,
@@ -511,6 +547,17 @@ define([
511547
512548 renderRealnameField ( ) {
513549 const fieldState = this . state . form . fields . realname ;
550+ const messageClass = ( ( ) => {
551+ switch ( fieldState . status ) {
552+ case 'VALID' :
553+ return 'success' ;
554+ case 'LOCAL_VALID' :
555+ return 'success' ;
556+ case 'INVALID' :
557+ return 'danger' ;
558+ }
559+
560+ } ) ( ) ;
514561 const field = html `
515562 < div className =${ `form-group ${ this . getFieldBorderClass ( 'realname' ) } ` } style =${ { padding : '2px' } } >
516563 < label for ="signup_realname ">
@@ -524,7 +571,7 @@ define([
524571 value =${ fieldState . value }
525572 onInput =${ ( e ) => { return this . updateField ( 'realname' , e . target . value ) ; } }
526573 />
527- < div className ="text-danger "
574+ < div className ="text- ${ messageClass } "
528575 style =${ { padding : '4px' } } >
529576 ${ fieldState . validationMessage }
530577 </ div >
@@ -604,7 +651,7 @@ define([
604651 }
605652
606653 renderUsernameField ( ) {
607- const field = this . renderInputField ( 'username' ) ;
654+ const field = this . renderLookupInputField ( 'username' ) ;
608655 const info = html `
609656 < div >
610657 < div >
@@ -646,13 +693,25 @@ define([
646693 renderField ( fieldName , control ) {
647694 const fieldDefinition = this . getFieldDefinition ( fieldName ) ;
648695 const fieldState = this . state . form . fields [ fieldName ] ;
696+ const messageClassName = ( ( ) => {
697+ switch ( fieldState . status ) {
698+ case 'VALID' :
699+ return 'text-success' ;
700+ case 'LOCAL_VALID' :
701+ return 'text-warning' ;
702+ case 'REMOTE_VALIDATING' :
703+ return 'text-warning' ;
704+ case 'INVALID' :
705+ return 'text-danger' ;
706+ }
707+ } ) ( ) ;
649708 return html `
650709 < div className =${ `form-group ${ this . getFieldBorderClass ( fieldName ) } ` } style =${ { padding : '2px' } } >
651710 < label for =${ `signup_${ fieldName } ` } >
652711 ${ fieldDefinition . label } ${ this . requiredIcon ( fieldName ) }
653712 </ label >
654713 ${ control }
655- < div className =" text-danger "
714+ < div className =${ messageClassName }
656715 style =${ { padding : '4px' } } >
657716 ${ fieldState . validationMessage }
658717 </ div >
@@ -675,6 +734,94 @@ define([
675734 return this . renderField ( fieldName , control ) ;
676735 }
677736
737+ renderLookupInputField ( fieldName ) {
738+ const fieldState = this . state . form . fields [ fieldName ] ;
739+ const onLookup = async ( ) => {
740+ const validate = async ( value ) => {
741+ const auth2Client = new auth2 . Auth2 ( {
742+ baseUrl : this . props . runtime . config ( 'services.auth.url' )
743+ } ) ;
744+ try {
745+ const { availablename} = await auth2Client . loginUsernameSuggest ( value ) ;
746+ if ( availablename === value ) {
747+ return {
748+ isValid : true
749+ } ;
750+ }
751+ return {
752+ isValid : false ,
753+ message : `This username is not available: a suggested available username is ${ availablename } `
754+ } ;
755+ } catch ( ex ) {
756+ console . error ( 'error looking up username in auth' , ex ) ;
757+ }
758+ } ;
759+
760+ const value = this . state . form . fields [ fieldName ] . value ;
761+ this . setFieldState ( fieldName , {
762+ value, isModified : true , status : 'REMOTE_VALIDATING' ,
763+ validationMessage : html `Checking if username is available at KBase... < span className ="fa fa-spinner fa-pulse " /> `
764+ } ) ;
765+ const { isValid, message} = await validate ( value ) ;
766+ const fieldState = ( ( ) => {
767+ if ( isValid ) {
768+ return {
769+ value,
770+ isModified : true ,
771+ status : 'VALID' ,
772+ validationMessage : 'This username is available'
773+ } ;
774+ }
775+ return {
776+ value,
777+ isModified : true ,
778+ status : 'INVALID' ,
779+ validationMessage : message
780+ } ;
781+ } ) ( ) ;
782+ this . setFieldState ( fieldName , fieldState ) ;
783+ } ;
784+ // const buttonMessage = (() => {
785+ // switch (fieldState.status) {
786+ // case 'REQUIRED_MISSING':
787+ // return 'Check Username with KBase';
788+ // case 'VALID':
789+ // return 'Username available';
790+ // case 'LOCAL_VALID':
791+ // return 'Check Username with KBase';
792+ // case 'REMOTE_VALIDATING':
793+ // return 'Checking for availability...';
794+ // case 'INVALID':
795+ // return 'Check Username with KBase';
796+ // }
797+ // return fieldState.status;
798+ // })();
799+
800+
801+ const control = html `
802+ < div style =${ { display : 'flex' , flexDirection : 'row' } } >
803+ < input type ="text "
804+ style =${ { flex : '1 1 0' } }
805+ className ="form-control"
806+ id=${ `signup_${ fieldName } ` }
807+ name=${ fieldName }
808+ autocomplete="off"
809+ value=${ fieldState . value }
810+ onInput=${ ( e ) => { this . updateField ( fieldName , e . target . value ) ; } }
811+ />
812+ < button type ="button "
813+ style =${ { flex : '0 0 auto' } }
814+ className ="btn btn-primary"
815+ disabled=${ fieldState . status !== 'LOCAL_VALID' }
816+ onClick=${ onLookup }
817+ >
818+ Check for Availability
819+ </ button >
820+ </ div >
821+ ` ;
822+ return this . renderField ( fieldName , control ) ;
823+ }
824+
678825 renderDescriptionField ( ) {
679826 const field = this . renderInputField ( 'department' ) ;
680827 const info = '' ;
0 commit comments