Tcomb-form-native: Question: How can I implement a (password) textinput with a button inside, to show/reveal the password when tapped?

Created on 12 Jul 2016  路  11Comments  路  Source: gcanti/tcomb-form-native

I was just wondering what path to take to implement what the title says using your beautiful library.!

Most helpful comment

Ok for people looking for this in the future, I have created it and it looks like this:

screen shot 2016-07-12 at 18 59 19

PasswordTemplate.js:

import React from 'react';
import { View, Text, TextInput, TouchableOpacity } from 'react-native';

function passwordTextbox(locals) {
  if (locals.hidden) {
    return null;
  }

  var stylesheet = locals.stylesheet;
  var formGroupStyle = stylesheet.formGroup.normal;
  var controlLabelStyle = stylesheet.controlLabel.normal;
  var passwordTextboxStyleContainer = stylesheet.passwordTextboxStyleContainer.normal;
  var passwordTextboxBtn = stylesheet.passwordTextboxBtn;
  var passwordTextboxBtnTxt = stylesheet.passwordTextboxBtnTxt;

  var passwordTextboxStyle = stylesheet.textbox.normal;

  var helpBlockStyle = stylesheet.helpBlock.normal;
  var errorBlockStyle = stylesheet.errorBlock;

  if (locals.hasError) {
    formGroupStyle = stylesheet.formGroup.error;
    passwordTextboxStyleContainer = stylesheet.passwordTextboxStyleContainer.error;
    controlLabelStyle = stylesheet.controlLabel.error;
    passwordTextboxStyle = stylesheet.textbox.error;
    helpBlockStyle = stylesheet.helpBlock.error;
  }

  if (locals.editable === false) {
    passwordTextboxStyle = stylesheet.textbox.notEditable;
    passwordTextboxStyleContainer = stylesheet.passwordTextboxStyleContainer.notEditable;
  }

  var label = locals.label ? <Text style={controlLabelStyle}>{locals.label}</Text> : null;
  var help = locals.help ? <Text style={helpBlockStyle}>{locals.help}</Text> : null;
  var error = locals.hasError && locals.error ? <Text accessibilityLiveRegion="polite" style={errorBlockStyle}>{locals.error}</Text> : null;
  return (
    <View style={formGroupStyle}>
      {label}
      <View style={[{flexDirection:'row', justifyContent:'center', borderColor: 'black', borderRadius:2, borderWidth: 1,}, passwordTextboxStyleContainer]}>
        <View style={{flex:1}}>
          <TextInput
            accessibilityLabel={locals.label}
            ref="input"
            autoCapitalize={locals.autoCapitalize}
            autoCorrect={locals.autoCorrect}
            autoFocus={locals.autoFocus}
            blurOnSubmit={locals.blurOnSubmit}
            editable={locals.editable}
            keyboardType={locals.keyboardType}
            maxLength={locals.maxLength}
            multiline={locals.multiline}
            onBlur={locals.onBlur}
            onEndEditing={locals.onEndEditing}
            onFocus={locals.onFocus}
            onLayout={locals.onLayout}
            onSelectionChange={locals.onSelectionChange}
            onSubmitEditing={locals.onSubmitEditing}
            placeholderTextColor={locals.placeholderTextColor}
            secureTextEntry={locals.secureTextEntry}
            selectTextOnFocus={locals.selectTextOnFocus}
            selectionColor={locals.selectionColor}
            numberOfLines={locals.numberOfLines}
            underlineColorAndroid={locals.underlineColorAndroid}
            clearButtonMode={locals.clearButtonMode}
            clearTextOnFocus={locals.clearTextOnFocus}
            enablesReturnKeyAutomatically={locals.enablesReturnKeyAutomatically}
            keyboardAppearance={locals.keyboardAppearance}
            onKeyPress={locals.onKeyPress}
            returnKeyType={locals.returnKeyType}
            selectionState={locals.selectionState}
            onChangeText={(value) => locals.onChange(value)}
            onChange={locals.onChangeNative}
            placeholder={locals.placeholder}
            style={[{flex:1},passwordTextboxStyle,{ borderWidth: 0}] }
            value={locals.value}
          />
        </View>

        <TouchableOpacity style={[{justifyContent:'center', paddingHorizontal:5}, passwordTextboxBtn]} onPress={(e)=>{if(!!locals.config.onShowPasswordClicked){locals.config.onShowPasswordClicked(locals.config.passwordHidden)}}}>
          <Text style={passwordTextboxBtnTxt}>{locals.config.passwordHidden===false?"Hide":"Show"}</Text>
        </TouchableOpacity>
      </View>
      {help}
      {error}
    </View>
  );
}

module.exports = passwordTextbox;

In order to use it, you have to provide the stylesheet and options for it like so:

let options = {
      stylesheet: stylesheet,
      auto: 'placeholders',
      fields: {
        password: {
          template: PasswordTemplate,
          label: 'Create a Password',
          maxLength: 20,
          secureTextEntry: !this.props.authFormFields.showPassword,
          editable: !this.props.isFetchingAuth,
          hasError: this.props.authFormFields.passwordHasError,
          error: 'Password length 6-20 characters, containing both a number and a capital letter.',
          placeholder: '*******',
          returnKeyType: 'next',
          onSubmitEditing: this.onPasswordFinishedEditing,
          blurOnSubmit : true,
          underlineColorAndroid: Colors.accentColor,
          autoCorrect: false,
          autoCapitalize:'none',
          placeholderTextColor: Colors.secondaryTextColor,
          // autoFocus: true,
          config:{
            passwordHidden: true,
            onShowPasswordClicked: this.onPasswordShowClicked.bind(this)
          }
        },


      }
    };

and style it something like this:

const INPUT_COLOR = Colors.thirdTextColor;
const DISABLED_COLOR = '#777777';
const DISABLED_BACKGROUND_COLOR = '#eeeeee';
const FONT_SIZE = 11;
const FONT_WEIGHT = '500';

const stylesheet = Object.freeze({
  fieldset: {
    flexDirection: 'column'
  },

  // the style applied to the container of all inputs
  formGroup: {
    normal: {
      flex:0,
      marginBottom: 10
    },
    error: {
      flex:0,
      marginBottom: 10
    }
  },
  controlLabel: {
    normal: {
      // backgroundColor:'red',
      color: Colors.fourthTextColor,
      fontSize: 11,
      marginBottom: 7,
      fontWeight: FONT_WEIGHT
    },
    // the style applied when a validation error occours
    error: {
      color: Colors.errorTextColor,
      fontSize: FONT_SIZE,
      marginBottom: 7,
      fontWeight: FONT_WEIGHT
    }
  },
  helpBlock: {
    normal: {
      color: Colors.helpTextColor,
      fontSize: FONT_SIZE,
      marginBottom: 2
    },
    // the style applied when a validation error occours
    error: {
      color: Colors.helpTextColor,
      fontSize: FONT_SIZE,
      marginBottom: 2
    }
  },
  errorBlock: {
    fontSize: 12,
    justifyContent: 'center',
    textAlign: 'center',
    color: Colors.errorTextColor

  },
  textbox: {
    normal: {
      color: INPUT_COLOR,
      fontSize: 10,
      height: 45,
      padding: 7,
      borderRadius: 4,
      borderColor: Colors.mainBorderColor,
      borderWidth: 1,
      // marginBottom: 5
    },
    // the style applied when a validation error occours
    error: {
      color: INPUT_COLOR,
      fontSize: FONT_SIZE,
      height: 45,
      padding: 7,
      borderRadius: 4,
      borderColor: Colors.errorTextColor,
      borderWidth: 1,
      // marginBottom: 5
    },
    // the style applied when the textbox is not editable
    notEditable: {
      fontSize: FONT_SIZE,
      height: 36,
      padding: 7,
      borderRadius: 4,
      borderColor: Colors.mainBorderColor,
      borderWidth: 1,
      // marginBottom: 5,
      color: DISABLED_COLOR,
      backgroundColor: DISABLED_BACKGROUND_COLOR
    }
  },
  passwordTextboxStyleContainer:{
    normal: {
      // height: 45,
      // padding: 7,
      borderRadius: 4,
      borderColor: Colors.mainBorderColor,
      borderWidth: 1,

      // marginBottom: 5
    },
    // the style applied when a validation error occours
    error: {
      borderRadius: 4,
      borderColor: Colors.errorTextColor,
      borderWidth: 1,
      // marginBottom: 5
    },
    // the style applied when the textbox is not editable
    notEditable: {

      borderRadius: 4,
      borderColor: Colors.mainBorderColor,
      borderWidth: 1,
      backgroundColor: DISABLED_BACKGROUND_COLOR
    }
  },
  passwordTextboxBtn:{
    paddingHorizontal:w*.025
  },
  passwordTextboxBtnTxt:{
    fontSize: 12,
    color: Colors.primaryColor
  }

});

Then finally:

  let passwordForm = t.struct({
      password: t.String
   });

    return (
      <Form ref="form"
        type={passwordForm}
        options={options}
        value={this.props.value}
        onChange={this.props.onChange}
      />
    );

All 11 comments

The simplest way would be to write a custom template:

(pseudo code)

class MyPasswordComponent extends React.Component {

  state = {
    passwordDisplayed: false
  }

  toggleDisplay() {
    this.setState({ passwordDisplayed: !this.state.passwordDisplayed })
  }

  render() {
    return <div onClick={this.toggleDisplay.bind(this)}>...your input here..</div>
  }

}

// override the default template https://github.com/gcanti/tcomb-form-native/blob/master/lib/templates/bootstrap/textbox.js#L4
function myPasswordTemplate(locals) {
  return ...<MyPasswordComponent /> ...
}

...

const Type = t.struct({
  password: t.String
})

const options = {
  fields: {
    password: {
      template: myPasswordTemplate
    }
  }
}

Thank you!

Ok for people looking for this in the future, I have created it and it looks like this:

screen shot 2016-07-12 at 18 59 19

PasswordTemplate.js:

import React from 'react';
import { View, Text, TextInput, TouchableOpacity } from 'react-native';

function passwordTextbox(locals) {
  if (locals.hidden) {
    return null;
  }

  var stylesheet = locals.stylesheet;
  var formGroupStyle = stylesheet.formGroup.normal;
  var controlLabelStyle = stylesheet.controlLabel.normal;
  var passwordTextboxStyleContainer = stylesheet.passwordTextboxStyleContainer.normal;
  var passwordTextboxBtn = stylesheet.passwordTextboxBtn;
  var passwordTextboxBtnTxt = stylesheet.passwordTextboxBtnTxt;

  var passwordTextboxStyle = stylesheet.textbox.normal;

  var helpBlockStyle = stylesheet.helpBlock.normal;
  var errorBlockStyle = stylesheet.errorBlock;

  if (locals.hasError) {
    formGroupStyle = stylesheet.formGroup.error;
    passwordTextboxStyleContainer = stylesheet.passwordTextboxStyleContainer.error;
    controlLabelStyle = stylesheet.controlLabel.error;
    passwordTextboxStyle = stylesheet.textbox.error;
    helpBlockStyle = stylesheet.helpBlock.error;
  }

  if (locals.editable === false) {
    passwordTextboxStyle = stylesheet.textbox.notEditable;
    passwordTextboxStyleContainer = stylesheet.passwordTextboxStyleContainer.notEditable;
  }

  var label = locals.label ? <Text style={controlLabelStyle}>{locals.label}</Text> : null;
  var help = locals.help ? <Text style={helpBlockStyle}>{locals.help}</Text> : null;
  var error = locals.hasError && locals.error ? <Text accessibilityLiveRegion="polite" style={errorBlockStyle}>{locals.error}</Text> : null;
  return (
    <View style={formGroupStyle}>
      {label}
      <View style={[{flexDirection:'row', justifyContent:'center', borderColor: 'black', borderRadius:2, borderWidth: 1,}, passwordTextboxStyleContainer]}>
        <View style={{flex:1}}>
          <TextInput
            accessibilityLabel={locals.label}
            ref="input"
            autoCapitalize={locals.autoCapitalize}
            autoCorrect={locals.autoCorrect}
            autoFocus={locals.autoFocus}
            blurOnSubmit={locals.blurOnSubmit}
            editable={locals.editable}
            keyboardType={locals.keyboardType}
            maxLength={locals.maxLength}
            multiline={locals.multiline}
            onBlur={locals.onBlur}
            onEndEditing={locals.onEndEditing}
            onFocus={locals.onFocus}
            onLayout={locals.onLayout}
            onSelectionChange={locals.onSelectionChange}
            onSubmitEditing={locals.onSubmitEditing}
            placeholderTextColor={locals.placeholderTextColor}
            secureTextEntry={locals.secureTextEntry}
            selectTextOnFocus={locals.selectTextOnFocus}
            selectionColor={locals.selectionColor}
            numberOfLines={locals.numberOfLines}
            underlineColorAndroid={locals.underlineColorAndroid}
            clearButtonMode={locals.clearButtonMode}
            clearTextOnFocus={locals.clearTextOnFocus}
            enablesReturnKeyAutomatically={locals.enablesReturnKeyAutomatically}
            keyboardAppearance={locals.keyboardAppearance}
            onKeyPress={locals.onKeyPress}
            returnKeyType={locals.returnKeyType}
            selectionState={locals.selectionState}
            onChangeText={(value) => locals.onChange(value)}
            onChange={locals.onChangeNative}
            placeholder={locals.placeholder}
            style={[{flex:1},passwordTextboxStyle,{ borderWidth: 0}] }
            value={locals.value}
          />
        </View>

        <TouchableOpacity style={[{justifyContent:'center', paddingHorizontal:5}, passwordTextboxBtn]} onPress={(e)=>{if(!!locals.config.onShowPasswordClicked){locals.config.onShowPasswordClicked(locals.config.passwordHidden)}}}>
          <Text style={passwordTextboxBtnTxt}>{locals.config.passwordHidden===false?"Hide":"Show"}</Text>
        </TouchableOpacity>
      </View>
      {help}
      {error}
    </View>
  );
}

module.exports = passwordTextbox;

In order to use it, you have to provide the stylesheet and options for it like so:

let options = {
      stylesheet: stylesheet,
      auto: 'placeholders',
      fields: {
        password: {
          template: PasswordTemplate,
          label: 'Create a Password',
          maxLength: 20,
          secureTextEntry: !this.props.authFormFields.showPassword,
          editable: !this.props.isFetchingAuth,
          hasError: this.props.authFormFields.passwordHasError,
          error: 'Password length 6-20 characters, containing both a number and a capital letter.',
          placeholder: '*******',
          returnKeyType: 'next',
          onSubmitEditing: this.onPasswordFinishedEditing,
          blurOnSubmit : true,
          underlineColorAndroid: Colors.accentColor,
          autoCorrect: false,
          autoCapitalize:'none',
          placeholderTextColor: Colors.secondaryTextColor,
          // autoFocus: true,
          config:{
            passwordHidden: true,
            onShowPasswordClicked: this.onPasswordShowClicked.bind(this)
          }
        },


      }
    };

and style it something like this:

const INPUT_COLOR = Colors.thirdTextColor;
const DISABLED_COLOR = '#777777';
const DISABLED_BACKGROUND_COLOR = '#eeeeee';
const FONT_SIZE = 11;
const FONT_WEIGHT = '500';

const stylesheet = Object.freeze({
  fieldset: {
    flexDirection: 'column'
  },

  // the style applied to the container of all inputs
  formGroup: {
    normal: {
      flex:0,
      marginBottom: 10
    },
    error: {
      flex:0,
      marginBottom: 10
    }
  },
  controlLabel: {
    normal: {
      // backgroundColor:'red',
      color: Colors.fourthTextColor,
      fontSize: 11,
      marginBottom: 7,
      fontWeight: FONT_WEIGHT
    },
    // the style applied when a validation error occours
    error: {
      color: Colors.errorTextColor,
      fontSize: FONT_SIZE,
      marginBottom: 7,
      fontWeight: FONT_WEIGHT
    }
  },
  helpBlock: {
    normal: {
      color: Colors.helpTextColor,
      fontSize: FONT_SIZE,
      marginBottom: 2
    },
    // the style applied when a validation error occours
    error: {
      color: Colors.helpTextColor,
      fontSize: FONT_SIZE,
      marginBottom: 2
    }
  },
  errorBlock: {
    fontSize: 12,
    justifyContent: 'center',
    textAlign: 'center',
    color: Colors.errorTextColor

  },
  textbox: {
    normal: {
      color: INPUT_COLOR,
      fontSize: 10,
      height: 45,
      padding: 7,
      borderRadius: 4,
      borderColor: Colors.mainBorderColor,
      borderWidth: 1,
      // marginBottom: 5
    },
    // the style applied when a validation error occours
    error: {
      color: INPUT_COLOR,
      fontSize: FONT_SIZE,
      height: 45,
      padding: 7,
      borderRadius: 4,
      borderColor: Colors.errorTextColor,
      borderWidth: 1,
      // marginBottom: 5
    },
    // the style applied when the textbox is not editable
    notEditable: {
      fontSize: FONT_SIZE,
      height: 36,
      padding: 7,
      borderRadius: 4,
      borderColor: Colors.mainBorderColor,
      borderWidth: 1,
      // marginBottom: 5,
      color: DISABLED_COLOR,
      backgroundColor: DISABLED_BACKGROUND_COLOR
    }
  },
  passwordTextboxStyleContainer:{
    normal: {
      // height: 45,
      // padding: 7,
      borderRadius: 4,
      borderColor: Colors.mainBorderColor,
      borderWidth: 1,

      // marginBottom: 5
    },
    // the style applied when a validation error occours
    error: {
      borderRadius: 4,
      borderColor: Colors.errorTextColor,
      borderWidth: 1,
      // marginBottom: 5
    },
    // the style applied when the textbox is not editable
    notEditable: {

      borderRadius: 4,
      borderColor: Colors.mainBorderColor,
      borderWidth: 1,
      backgroundColor: DISABLED_BACKGROUND_COLOR
    }
  },
  passwordTextboxBtn:{
    paddingHorizontal:w*.025
  },
  passwordTextboxBtnTxt:{
    fontSize: 12,
    color: Colors.primaryColor
  }

});

Then finally:

  let passwordForm = t.struct({
      password: t.String
   });

    return (
      <Form ref="form"
        type={passwordForm}
        options={options}
        value={this.props.value}
        onChange={this.props.onChange}
      />
    );

@gcanti if you find time you could perhaps add it to your library, because a password input with such a show/hide label button I think is necessary to lots of people.

@SudoPlz I want to keep this library lean, a useful component like yours may be published as a standalone component though

@SudoPlz - how do i use it in my signing page?

this is my page:

import React from 'react';
import {View,Text,Image,TextInput,TouchableOpacity,Dimensions,StatusBar,Platform} from 'react-native';
var StyleSheet = require('WS1StyleSheet');
import {connect} from 'react-redux';
import t from 'react-native-i18n';
import Icon from 'react-native-vector-icons/MaterialIcons';
var f = require('tcomb-form-native');
var Form = f.form.Form;

// here we are: define your domain model

var LoginForm = f.struct({
email: f.String, // a required string
password: f.String, // a required string
});

import {mergeFormCss} from 'common/formCss';
import {textbox} from 'lib/MKTextbox';

var options = {
stylesheet:mergeFormCss({
textbox: {
normal: {
textAlign:t.t('dir') == 'rtl' && Platform.OS == 'android'?'right':'left',
height: 30, // have to do it on iOS
color:'white',
},
}
},'flat'),
fields: {
email: {
placeholder : t.t('Email'),
auto: 'placeholders',
error: t.t('Email is empty'),
template: (locals) => textbox(locals ,),
},
password: {
placeholder : t.t('Password'),
auto: 'placeholders',
error: t.t('Password is empty'),
password:true,
secureTextEntry:true,
template: (locals) => textbox(locals, ),
}
}
};

//Load data
var {
loadShipments,
loadOrders
} = require('./../../models/actions');

var Signin = React.createClass({
getInitialState() {
return {
value:{

    email : '',

    password : '',
  },
        errorMessage : '',
        loading: false
    };
},

componentWillMount(){
StatusBar.setBarStyle('light-content');
},
onChange(value) {
this.setState({value});
},
render: function(){

    return (

      <View style={[styles.headerWrapper,this.border('red')]}>

         <Text style={{textAlign: 'center',color:'white',fontSize:25,marginTop:5}}>{t.t('Login')}</Text>
      </View>

      <View style={[styles.formWrapper,this.border('green')]}>
        <View style={[styles.formWrapper_form,,this.border('yellow')]}>

          <View style={[styles.formlogin]}>

            {this.loginform()}

          </View>
        </View>

        <View style={[styles.formWrapper_button,this.border('green')]}>
            {this.loginbutton()}
        </View>
      </View>
    </View>










    );
},

loginform: function(){
return

    <Form
         ref="form"
         type={LoginForm}
         value={this.state.value}
         onChange={this.onChange}
         options={options}
       />
  </View>

},
loginbutton: function(){
return

    <AS1Button
          isLoading={this.state.loading}
          textStyle={{fontSize: 26,color:'white'}}
          style={{backgroundColor:'#F66A3B',height:43,width:300}}
          onPress={this.logInButton}
          >
          {t.t('Login')}
        </WS1Button>
  </View>

},
border: function(color){
return {
borderColor:color,
borderWidth: 0,
}
},

logInButton() {

    var value = this.refs.form.getValue();
    if (value) { 
        this.setState({loading:true});
  Api.login({
    email:value.email,
    password:value.password
  })

        this.setState({loading:false});


    }

},

});

var styles = StyleSheet.create({
container: {
flex:1,

});

module.exports = connect()(Signin);

@abuammar I believe you're not passing the right template.

var options = {
    stylesheet: mergeFormCss({
        textbox: {
            normal: {
                textAlign: t.t('dir') == 'rtl' && Platform.OS == 'android' ? 'right' : 'left',
                height: 30, // have to do it on iOS
                color: 'white',
            },
        }
    }, 'flat'),
    fields: {
        email: {
            placeholder: t.t('Email'),
            auto: 'placeholders',
            error: t.t('Email is empty'),
            template: (locals) => textbox(locals, ),
        },
        password: {
            placeholder: t.t('Password'),
            auto: 'placeholders',
            error: t.t('Password is empty'),
            password: true,
            secureTextEntry: true,
            template: (locals) => textbox(locals, ), // <--- Here you have to pass the PasswordTemplate
        }
    }
};

already in lib for hide and show password? @gcanti

bro @SudoPlz how to change value passwordHidden and secureTextEntry when click show/hide?

@otikindriyana it's been 4 years, I don't even remember TBH, and we no longer use this library :/

already solved
add:
`
onPasswordShowClicked(){

if(this.state.options.fields.confirmPassword.secureTextEntry == true){
  var options = t.update(this.state.options, {
    fields: {
      confirmPassword: {
        secureTextEntry: {'$set': false},
        config:{
          passwordHidden: {'$set': false},
        }
      }
    }
  });
  this.setState({options: options});
}else if(this.state.options.fields.confirmPassword.secureTextEntry == false){
  var options = t.update(this.state.options, {
    fields: {
      confirmPassword: {
        secureTextEntry: {'$set': true},
        config:{
          passwordHidden: {'$set': true},
        }
      }
    }
  });
  this.setState({options: options});
}

}
`

Was this page helpful?
0 / 5 - 0 ratings

Related issues

dvlprmartins picture dvlprmartins  路  4Comments

alexicum picture alexicum  路  6Comments

scarlac picture scarlac  路  4Comments

flyingace picture flyingace  路  5Comments

abdelghafourzguindou picture abdelghafourzguindou  路  4Comments