├── .gitignore ├── LICENSE ├── README.md ├── ejectTheme.js ├── package.json └── src ├── components └── panel │ ├── index.js │ └── styles.js ├── fields ├── date │ ├── index.js │ └── styles.js ├── form │ └── index.js ├── picker │ ├── index.js │ └── styles.js ├── select │ ├── index.js │ └── styles.js ├── switch │ ├── index.js │ └── styles.js └── textInput │ ├── index.js │ └── styles.js ├── formBuilder └── index.js ├── index.js ├── styles.js ├── theme.js └── utils ├── config.js ├── methods.js └── validators.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | #DS STORE 40 | .DS_Store 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Kuldeep Saxena 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM Version](https://img.shields.io/npm/v/react-native-form-builder.svg?style=flat)](https://www.npmjs.com/package/react-native-form-builder) 2 | # react-native-form-builder 3 | ![alt text](http://g.recordit.co/7PqX8Ft7VO.gif) 4 | ![alt text](http://g.recordit.co/RWFvqi5tXG.gif) 5 | # Note: 6 | If you're looking for a better form management library with more advanced features, Please check out [React Reactive Form](https://github.com/bietkul/react-reactive-form). 7 | ## Features 8 | - Generate Form Fields UI 9 | - Manage, track state & values of fields 10 | - Automatically manages focus to next field on submit (TextInput) 11 | - Handle all keyboard related problems smartly 12 | - Supports custom validations & nested forms 13 | - Uses Nativebase components 14 | 15 | ## Getting Started 16 | 17 | - [Installation](#installation) 18 | - [Basic Usage](#basic-usage) 19 | - [Properties](#properties) 20 | + [Basic](#basic) 21 | + [Methods](#methods) 22 | - [Form Fields](#form-fields) 23 | + [Field Structure](#field-structure) 24 | + [Common Properties to all Fields](#common-properties-to-all-fields) 25 | - [Field Types](#field-types) 26 | + [TextInput](#textinput) 27 | + [Picker](#picker) 28 | + [Switch](#switch) 29 | + [Date](#date) 30 | + [Select](#select) 31 | + [Nested Forms](#nested-forms) 32 | + [Prefilling Form's Values](#prefill-form-values) 33 | + [Add Custom Validations](#add-custom-validations) 34 | + [Customize Your Form](#customize-your-form) 35 | + [Add custom components](#add-custom-components) 36 | + [Add custome error component](#add-custom-error-component) 37 | - [Example](#example) 38 | 39 | ## Installation 40 | 41 | `react-native-form-builder` requires a peer of [`native-base`](https://github.com/GeekyAnts/NativeBase) 42 | 43 | 44 | To Install the peer dependecy 45 | ``` 46 | $ npm i native-base --save 47 | 48 | ``` 49 | link the peer dependecy using 50 | 51 | ``` 52 | react-native link 53 | ``` 54 | and then insteall `react-native-form-builder` 55 | 56 | ```bash 57 | $ npm i react-native-form-builder --save 58 | ``` 59 | 60 | ## Basic Usage 61 | 62 | - Install `react-native` first 63 | 64 | ```bash 65 | $ npm i react-native -g 66 | ``` 67 | - Initialization of a react-native project 68 | 69 | ```bash 70 | $ react-native init myproject 71 | ``` 72 | 73 | - Then, edit `myproject/index.ios.js`, like this: 74 | Example: Login Form consisting of three fields (username, password, country) 75 | 76 | ```jsx 77 | import React, { Component } from 'react'; 78 | import { AppRegistry } from 'react-native'; 79 | import { View, Text, Button } from 'native-base'; 80 | import GenerateForm from 'react-native-form-builder'; 81 | 82 | const styles = { 83 | wrapper: { 84 | flex: 1, 85 | marginTop: 150, 86 | }, 87 | submitButton: { 88 | paddingHorizontal: 10, 89 | paddingTop: 20, 90 | }, 91 | }; 92 | // These Fields will create a login form with three fields 93 | const fields = [ 94 | { 95 | type: 'text', 96 | name: 'user_name', 97 | required: true, 98 | icon: 'ios-person', 99 | label: 'Username', 100 | }, 101 | { 102 | type: 'password', 103 | name: 'password', 104 | icon: 'ios-lock', 105 | required: true, 106 | label: 'Password', 107 | }, 108 | { 109 | type: 'picker', 110 | name: 'country', 111 | mode: 'dialog', 112 | label: 'Select Country', 113 | defaultValue: 'INDIA', 114 | options: ['US', 'INDIA', 'UK', 'CHINA', 'FRANCE'], 115 | }, 116 | ]; 117 | export default class FormGenerator extends Component { 118 | login() { 119 | const formValues = this.formGenerator.getValues(); 120 | console.log('FORM VALUES', formValues); 121 | } 122 | render() { 123 | return ( 124 | 125 | 126 | { 128 | this.formGenerator = c; 129 | }} 130 | fields={fields} 131 | /> 132 | 133 | 134 | 137 | 138 | 139 | ); 140 | } 141 | } 142 | 143 | AppRegistry.registerComponent('FormGenerator', () => FormGenerator); 144 | ``` 145 | ## Properties 146 | ### Basic 147 | | Prop | Default | Type | Description | 148 | | :------------ |:---------------:| :---------------:| :-----| 149 | | autoValidation | true | `bool` | if `false` then auto validation script will not run i.e formBuilder will not validate form data automatically. | 150 | | onValueChange | N/A | `function` | Invoked every time after a field changes it's value | 151 | | customValidation | N/A | `function` | This function will be triggered everytime before a field changes it's value, here you can write your custom validation script & set error accordingly. | 152 | | customComponents | N/A | `object` | To define your custom type of components.| 153 | | formData | N/A | `object` | To prefill the form values.| 154 | | fields | `required` | `array` | Array of form fields. | 155 | | scrollViewProps | N/A | `object` | Scrollview custom props. | 156 | | errorComponent | N/A | `React Component` | Custom error display component. | 157 | 158 | ### Methods: 159 | Currently, these methods are available in FormBuilder, you can access them by using ref property. 160 | 161 | ### getValues 162 | To extract the values of form fields. 163 | Returns: An object consisting of field values (fieldName: value). 164 | for e.g 165 | ``` 166 | { 167 | username: 'bietkul' 168 | password: 'bietkul@git' 169 | } 170 | ``` 171 | ### setValues 172 | Forcefully set values for particular fields.
173 | Parameters: An object of key value pairs(`name: value`).
174 | name: Field name for which value has to be set.
175 | value: Value for that particular field 176 | For e.g 177 | ``` 178 | { name1: value1, name2: value2, .....} 179 | ``` 180 | ### resetForm 181 | Reset Form values as well as errors. 182 | ### setToDefault 183 | Forcefully set values to default for particular fields. 184 | Parameters: An array of strings, where every string is the name of a field for which value has to be set as default value. 185 | For e.g 186 | ``` 187 | [fieldName1, fieldName2, fieldName3 .....] 188 | ``` 189 | 190 | ### Form Fields 191 | ### Field Structure 192 | A field is an object which has the properties required to generate it. 193 | It looks something like that : 194 | ```jsx 195 | { 196 | type: 'text', 197 | name: 'user_name', 198 | label: 'Username' 199 | } 200 | ``` 201 | 202 | ### Common Properties to all Fields 203 | These properties are applicable on all fields. 204 | 205 | | Property | Required | Type | Description | 206 | | :------------ |:---------------:| :---------------:| :-----| 207 | | type | `yes` | `enum` only possible values are : { text, password, group, email, number, url, select, switch, date } | To define type of field. | 208 | | name | `yes` | `string` | Every field should has a name which works as an unique identifier for that field. | 209 | | label | `yes` | `string` | Field label to display. | 210 | | editable | `No` | `bool` | To decide that whether a field is editable or not. | 211 | | required | `No` | `bool` | Helps to decide if a field can has empty value or not. Doesn't work in case of `autoValidation = false` . | 212 | | defaultValue | `No` | `Depends on field's type` | Sets the intial value for that field before the form initialization. | 213 | | hidden | `No` | `bool` | If `true` then a field will not be displayed. | 214 | 215 | ### Field Types 216 | ### TextInput 217 | Supports all these kind of types :- 218 | text, 219 | password, 220 | email, 221 | url, 222 | number 223 | 224 | #### Extra Properties 225 | | Props | Default | Type | Description | 226 | | :------------ |:--------------- |:---------------| :-----| 227 | | iconName | N/A | `string` | Sets the icon, you can use any icon name which is available in `react-native-vector-icons`| 228 | | iconOrientaion | `left (default)` or `right` | `string` | Adjust icon orientation | 229 | | props | N/A | `object` | Here you can define extra props which are applicable for react native TextInput Component. For e.g. { multiline: true, secureTextEntry : true .... } 230 | 231 | #### Value Type : `String` ( Except for type `number` ) 232 | 233 | ### Picker 234 | `type: picker` 235 | Uses native picker 236 | 237 | #### Extra Properties 238 | | Props | Default | Type | Description | 239 | | :------------ |:--------------- |:---------------| :-----| 240 | | options (required) | N/A | `array` | An array of strings to define available options for e.g. ['CAR', 'BIKE', 'BICYCLE'] | 241 | | props | N/A | `object` | Here you can define extra props which are applicable of react native Picker Component for e.g. { mode: 'dropdown', .... } 242 | 243 | #### Value Type : `String` 244 | 245 | #### Default Value Type : 246 | You can set default value as a string which must be present in available options. 247 | For e.g If options are ['CAR', 'BIKE', 'BICYCLE'] then you can define `defaultValue = 'BIKE'` 248 | 249 | ### Switch 250 | `(type: switch)` 251 | It's an implement of React Native `switch` component. 252 | 253 | #### Value Type : `Boolean` 254 | 255 | ### Date 256 | `(type: string)` 257 | 258 | #### Extra Properties 259 | | Props | Default | Type | Description | 260 | | :------------ |:--------------- |:---------------| :-----| 261 | | mode | `datetime` | `string` | To define type of date picker, available values are `date, time, datetime` | 262 | | maxDate | N/A | `string` or `JS Date object` | To define the maximum date can be select in date picker | 263 | | minDate | N/A | `string` or `JS Date object` | To define the minimum date can be select in date picker | 264 | 265 | 266 | #### Value Type : `String` 267 | #### Default Value Type : `string` or `JS Date object` 268 | 269 | ### Select 270 | 271 | #### Extra Properties 272 | | Props | Required | Default | Type | Description | 273 | | :------------| :------------ |:--------------- |:---------------| :-----| 274 | | multiple | `No` | `false` | `bool` | To define that the field can accept multple values or not i.e user can select multiple values at a time or not. | 275 | | objectType | `No` | `false` | `string` | To define that the values can be of object or not.If `true`, then you need to specify `labelKey` & `primaryKey` | 276 | | labelKey | `Yes` if `objectType = true` | `N/A` | `string` | To define the key which value need to be used as label. | 277 | | primaryKey | `Yes` if `objectType = true` | `N/A` | `string` | To define the key which is unique in all objects. | 278 | | options | `Yes` | `N/A` | array of `objects` or `strings` | An array of `objects` or `strings` containing all available options.| 279 | 280 | #### Value Type : Array of `Strings` or `Objects` 281 | #### Array of Strings 282 | For e.g. `options = ['CAR', 'BIKE', 'BICYCLE']` 283 | #### Array of Objects 284 | If you're using array of objects then please don't forget to define these properties: 285 | ```jsx 286 | objectType: true, 287 | labelKey: 'name', // For Below example 288 | primaryKey: 'id', // For Below example 289 | ``` 290 | For e.g. 291 | ``` 292 | options: [ 293 | { 294 | id: 1, 295 | name: 'CAR', 296 | }, 297 | { 298 | id: 2, 299 | name: 'BIKE', 300 | }, 301 | { 302 | id: 3, 303 | name: 'BICYCLE', 304 | }, 305 | ] 306 | ``` 307 | #### Default Value Type : `string` or `JS Date object` 308 | In case of object values: 309 | ``` 310 | defaultValue: [{ 311 | id: 3, 312 | name: 'kuldeep2', 313 | title: 'saxena2', 314 | }], 315 | ``` 316 | In case of string values: 317 | ``` 318 | defaultValue: ['CAR', 'BIKE'], 319 | ``` 320 | 321 | ## Nested Forms 322 | `(type: group)` 323 | Form Builder also supports nested forms, some times you need to wrap all of your form values in an object or we can say that you have some nested fields, in this case you can define `group` fields. 324 | An example will better explain it: 325 | 326 | ``` 327 | { 328 | type: 'group', 329 | name: 'address', 330 | label: 'Address', 331 | fields: [ 332 | { 333 | type: 'text', 334 | name: 'city', 335 | label: 'City', 336 | }, 337 | { 338 | type: 'picker', 339 | name: 'country', 340 | label: 'Country', 341 | defaultValue: 'INDIA', 342 | options: ['US', 'INDIA', 'UK', 'CHINA', 'FRANCE'], 343 | }, 344 | ], 345 | }, 346 | ``` 347 | #### Value Type : `Object` 348 | 349 | For above example the return value object will be something like that: 350 | ``` 351 | { city: 'Bangalore', country: 'INDIA' } 352 | ``` 353 | #### Default Value Type : `Object` 354 | 355 | You can set default value for above example like that: 356 | ``` 357 | { city: 'Newyork', country: 'US' } 358 | ``` 359 | ## Prefill Form Values 360 | This feature of formBuilder is very helpful in case of edit mode i.e if you want to edit the values of a form then you can easily prefill the form by using `formData` prop.
361 | For e.g 362 | ``` 363 | formData = { 364 | first_name : 'Jon', 365 | last_name: 'Snow', 366 | house: 'Winterfell', 367 | status: 'Sad' 368 | } 369 | ``` 370 | 371 | ## Add Custom Validations 372 | 373 | It's very easy to add your custom validations & error messages with FormBuilder.All you need to do is define a function & pass it as `customValidation` prop. 374 | 375 | For e.g. 376 | ``` 377 | function validate(field) { 378 | let error = false; 379 | let errorMsg = ''; 380 | if (field.name === 'username' && !(field.value && field.value.trim())) { 381 | error = true; 382 | errorMsg = 'Username is required'; 383 | } 384 | if (field.name === 'password' && !(field.value && field.value.trim())) { 385 | error = true; 386 | errorMsg = 'Password is required'; 387 | } 388 | return { error, errorMsg }; 389 | } 390 | ``` 391 | Note: Always return an object which will have two entities `error` type of `boolean` & `errorMsg` type of `string`. 392 | 393 | ## Customize your form 394 | - Eject Theme by running this command 395 | ```bash 396 | node node_modules/react-native-form-builder/ejectTheme.js 397 | ``` 398 | It will create a file named `form-theme.js` in your project's root folder. 399 | 400 | Customize your theme. 401 | 402 | Import theme from `form-them`. 403 | 404 | Use it by passing as `theme` prop. 405 | ``` 406 | import theme from '../form-theme'; 407 | .... 408 | { this.formGenerator = c; }} 410 | theme = {theme} 411 | .... 412 | /> 413 | ``` 414 | ## Add Custom Components 415 | Build your custom type's components & handle them easily with the help of form builder. 416 | Use the `customComponents` prop of form builder. 417 | ### Prototype 418 | It's an object of key value pair where key will be the `type` of the component & value will be your custom Component.

419 | ```customComponents = { type1: {component: ComponentName1, props: Props }, type2: {component: ComponentName2} .....}``` 420 | 421 | ### How To Use 422 | - Define your custom `type` in field's object. 423 | - Form builder extends the props of your component by adding some extra props. 424 | - You can also pass some extra props in your custom components. 425 | - In your component you can access these props to handle the state of the field. 426 | 427 | | Props | Type | Description | 428 | | :------------| :------| :-----| 429 | | attributes | `object` | In this prop you can access the field's attributes (value, error, errorMsg ....). | 430 | | updateValue(fieldName, Value) | `function` | You need to use this method to update the value of the field | 431 | | onSummitTextInput(fieldName) | `function`| If you're using TextInput then you can use this function to automatically manage the text input focus.For example you can define it in the `onSubmitEditing` prop of TextInput 432 | | theme | `object` | Use the theme variables to style your component 433 | 434 | ## Add Custom Error Component 435 | - Now you can use your custom error component to display error messages. 436 | - In your custom component you will receive two props `attributes` & `theme` variables. 437 | - You can access the error & error message as a property of the attributes object. 438 | 439 | 440 | ## Example 441 | The form in second gif is generated by these fields 442 | 443 | -------------------------------------------------------------------------------- /ejectTheme.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const path = require('path'); 3 | 4 | const printMessage = require('print-message'); 5 | 6 | 7 | try { 8 | fs.copySync(path.join(__dirname, 'src', 'theme.js'), path.join(process.cwd(), 'form-theme.js')); 9 | printMessage([ 10 | `Form Builder theme has been copied at ${path.join(process.cwd(), 'form-theme.js')}`, 11 | 'Here\'s how to theme your form', 12 | '', 13 | 'import theme from \'./form-theme\';', 14 | 'export default class ThemeExample extends Component {', 15 | 'render() {', 16 | ' return (', 17 | ' ', 18 | ' ', 19 | ' ...', 20 | ' { this.formGenerator = c; }}', 22 | ' theme = {theme}', 23 | ' ....', 24 | ' />', 25 | ' ', 26 | ' ', 27 | ' );', 28 | '}', 29 | '', 30 | 'Head over to the docs(https://github.com/bietkul/react-native-form-builder) for detailed information on customization', 31 | ], { 32 | color: 'yellow', 33 | borderColor: 'green', 34 | }); 35 | } catch (err) { 36 | console.log(`Error: ${err}`); 37 | } 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-form-builder", 3 | "version": "1.0.16", 4 | "description": "Generate Awesome Forms In an easy way", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/bietkul/react-native-form-builder.git" 12 | }, 13 | "_npmUser": { 14 | "name": "anjuma", 15 | "email": "kuldepsaxena@155@gmail.com" 16 | }, 17 | "author": "Kuldeep Saxena", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/bietkul/react-native-form-builder/issues" 21 | }, 22 | "homepage": "https://github.com/bietkul/react-native-form-builder#readme", 23 | "keywords": [ 24 | "android", 25 | "ios", 26 | "react", 27 | "native", 28 | "react-native", 29 | "native-modules", 30 | "form", 31 | "form generator", 32 | "generator", 33 | "builder" 34 | ], 35 | "dependencies": { 36 | "fs-extra": "^3.0.1", 37 | "lodash": "^4.17.4", 38 | "print-message": "^2.1.0", 39 | "prop-types": "^15.6.0", 40 | "react-native-i18n": "^2.0.0", 41 | "react-native-keyboard-aware-scroll-view": "^0.8.0" 42 | }, 43 | "devDependencies": { 44 | "native-base": "^2.12.0", 45 | "react": "^16.8.0", 46 | "react-native": "0.60.0" 47 | }, 48 | "peerDependencies": { 49 | "react-native": ">=0.60.0", 50 | "react": ">=15.5.0", 51 | "native-base": "^2.12.0" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/components/panel/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react'; 3 | import { Animated, Dimensions, View, Easing, Keyboard } from 'react-native'; 4 | import styles from './styles'; 5 | 6 | class Panel extends Component { 7 | 8 | static propTypes = { 9 | children: PropTypes.object, 10 | } 11 | 12 | constructor(props) { 13 | super(props); 14 | 15 | const { height } = Dimensions.get('window'); 16 | 17 | this.state = { 18 | expanded: false, 19 | animation: new Animated.Value(0), 20 | maxHeight: (height / 4), 21 | minHeight: 0, 22 | }; 23 | this.toggle = this.toggle.bind(this); 24 | } 25 | 26 | toggle() { 27 | Keyboard.dismiss(); 28 | const initialValue = this.state.expanded ? 29 | (this.state.maxHeight + this.state.minHeight) : this.state.minHeight; 30 | const finalValue = this.state.expanded ? 31 | this.state.minHeight : (this.state.maxHeight + this.state.minHeight); 32 | 33 | this.setState({ 34 | expanded: !this.state.expanded, 35 | }); 36 | 37 | this.state.animation.setValue(initialValue); 38 | Animated.timing( 39 | this.state.animation, 40 | { 41 | toValue: finalValue, 42 | duration: 500, 43 | easing: Easing.InOut, 44 | } 45 | ).start(); 46 | } 47 | 48 | 49 | render() { 50 | return ( 51 | 52 | 53 | {this.props.children} 54 | 55 | 56 | ); 57 | } 58 | } 59 | export default Panel; -------------------------------------------------------------------------------- /src/components/panel/styles.js: -------------------------------------------------------------------------------- 1 | const React = require('react-native'); 2 | 3 | const { StyleSheet } = React; 4 | 5 | 6 | module.exports = StyleSheet.create({ 7 | 8 | container: { 9 | backgroundColor: '#fff', 10 | overflow: 'hidden', 11 | }, 12 | body: { 13 | paddingTop: 0, 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /src/fields/date/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react'; 3 | import { View, Text } from 'native-base'; 4 | import I18n from 'react-native-i18n'; 5 | import { Platform, DatePickerIOS, DatePickerAndroid, TouchableOpacity, TimePickerAndroid } from 'react-native'; 6 | import Panel from '../../components/panel'; 7 | 8 | export default class DatePickerField extends Component { 9 | static defaultProps = { 10 | timeZoneOffsetInHours: (-1) * ((new Date()).getTimezoneOffset() / 60), 11 | }; 12 | static propTypes = { 13 | attributes: PropTypes.object, 14 | updateValue: PropTypes.func, 15 | timeZoneOffsetInHours: PropTypes.number, 16 | theme: PropTypes.object, 17 | ErrorComponent: PropTypes.func, 18 | } 19 | constructor(props) { 20 | super(props); 21 | this.onDateChange = this.onDateChange.bind(this); 22 | this.showTimePicker = this.showTimePicker.bind(this); 23 | this.showDatePicker = this.showDatePicker.bind(this); 24 | } 25 | onDateChange(date) { 26 | this.props.updateValue(this.props.attributes.name, date); 27 | } 28 | showTimePicker = async (stateKey) => { 29 | const { attributes } = this.props; 30 | const currentDate = attributes.value ? new Date(attributes.value) : new Date(); 31 | try { 32 | const { action, minute, hour } = await TimePickerAndroid.open({ 33 | hour: currentDate.getHours(), 34 | minute: currentDate.getMinutes(), 35 | }); 36 | if (action === TimePickerAndroid.timeSetAction) { 37 | const date = currentDate; 38 | date.setHours(hour); 39 | date.setMinutes(minute); 40 | this.onDateChange(date); 41 | } 42 | } catch ({ code, message }) { 43 | console.warn(`Error in example '${stateKey}': `, message); 44 | } 45 | }; 46 | showDatePicker = async (stateKey) => { 47 | const { attributes } = this.props; 48 | const currentDate = attributes.value ? new Date(attributes.value) : new Date(); 49 | try { 50 | const { action, year, month, day } = await DatePickerAndroid.open( 51 | { 52 | date: currentDate, 53 | minDate: attributes.minDate && new Date(attributes.minDate), 54 | maxDate: attributes.maxDate && new Date(attributes.maxDate), 55 | } 56 | ); 57 | if (action !== DatePickerAndroid.dismissedAction) { 58 | const currentHour = currentDate.getHours(); 59 | const currentMinutes = currentDate.getMinutes(); 60 | const date = new Date(year, month, day); 61 | if (currentHour) { 62 | date.setHours(currentHour); 63 | } 64 | if (currentMinutes) { 65 | date.setMinutes(currentMinutes); 66 | } 67 | this.onDateChange(date); 68 | } 69 | } catch ({ code, message }) { 70 | console.warn(`Error in example '${stateKey}': `, message); 71 | } 72 | }; 73 | render() { 74 | const { theme, attributes, ErrorComponent } = this.props; 75 | const value = (attributes.value && new Date(attributes.value)) || null; 76 | const mode = attributes.mode || 'datetime'; 77 | return ( 78 | 79 | { (Platform.OS === 'ios') ? 80 | 90 | this.panel.toggle()} 92 | style={{ 93 | paddingVertical: 10, 94 | flexDirection: 'row', 95 | alignItems: 'center', 96 | justifyContent: 'space-between', 97 | }} 98 | > 99 | {attributes.label} 100 | 105 | { 106 | (mode ? 107 | (mode === 'date' 108 | || mode === 'datetime') 109 | : true) && 110 | 115 | 116 | { (value && I18n.strftime(value, '%d %b %Y')) || 'None' } 117 | 118 | 119 | } 120 | { 121 | (mode ? 122 | (mode === 'time' 123 | || mode === 'datetime') 124 | : true) && 125 | 130 | 131 | { (value && I18n.strftime(value, '%I:%M %p')) || 'None' } 132 | 133 | 134 | } 135 | 136 | 137 | 138 | { this.panel = c; }} 140 | > 141 | 149 | 150 | 151 | : 152 | 166 | {attributes.label} 167 | 172 | { 173 | (attributes.mode === 'date' 174 | || attributes.mode === 'datetime') 175 | && 176 | 182 | 183 | { (value && I18n.strftime(value, '%d %b %Y')) || 'Date' } 184 | 185 | 186 | } 187 | { 188 | (attributes.mode === 'time' 189 | || attributes.mode === 'datetime') 190 | && 191 | 196 | 197 | { (value && I18n.strftime(value, '%I:%M %p')) || 'Time' } 198 | 199 | 200 | } 201 | 202 | 203 | 204 | } 205 | 206 | ); 207 | } 208 | } -------------------------------------------------------------------------------- /src/fields/date/styles.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bietkul/react-native-form-builder/05fce486f48adf8a43b3da25853a7b08fb12d851/src/fields/date/styles.js -------------------------------------------------------------------------------- /src/fields/form/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react'; 3 | import { View, Text } from 'native-base'; 4 | import GenerateForm from '../../formBuilder'; 5 | 6 | export default class FormField extends Component { 7 | static propTypes = { 8 | attributes: PropTypes.object, 9 | theme: PropTypes.object, 10 | updateValue: PropTypes.func, 11 | autoValidation: PropTypes.bool, 12 | customValidation: PropTypes.func, 13 | customComponents: PropTypes.object, 14 | } 15 | constructor(props) { 16 | super(props); 17 | this.onValueChange = this.onValueChange.bind(this); 18 | } 19 | componentDidMount() { 20 | this.props.updateValue(this.props.attributes.name, this.group.getValues()); 21 | } 22 | onValueChange() { 23 | this.props.updateValue(this.props.attributes.name, this.group.getValues()); 24 | } 25 | handleChange(text) { 26 | this.setState({ 27 | value: text, 28 | }, () => this.props.updateValue(this.props.attributes.name, text)); 29 | } 30 | render() { 31 | const { 32 | attributes, 33 | theme, 34 | autoValidation, 35 | customValidation, 36 | customComponents, 37 | } = this.props; 38 | return ( 39 | 40 | 41 | {attributes.label} 42 | 43 | 44 | { this.group = c; }} 46 | onValueChange={this.onValueChange} 47 | autoValidation={autoValidation} 48 | customValidation={customValidation} 49 | customComponents={customComponents} 50 | showErrors 51 | fields={attributes.fields} 52 | theme={theme} 53 | /> 54 | 55 | 56 | ); 57 | } 58 | } -------------------------------------------------------------------------------- /src/fields/picker/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react'; 3 | import { View, Text } from 'native-base'; 4 | import { Platform, Picker, TouchableOpacity } from 'react-native'; 5 | import Panel from '../../components/panel'; 6 | import styles from './../../styles'; 7 | 8 | const Item = Picker.Item; 9 | export default class PickerField extends Component { 10 | static propTypes = { 11 | attributes: PropTypes.object, 12 | theme: PropTypes.object, 13 | updateValue: PropTypes.func, 14 | ErrorComponent: PropTypes.func, 15 | } 16 | handleChange(value) { 17 | const attributes = this.props.attributes; 18 | this.props.updateValue(attributes.name, attributes.options[value]); 19 | } 20 | render() { 21 | const { theme, attributes, ErrorComponent } = this.props; 22 | const isValueValid = attributes.options.indexOf(attributes.value) > -1; 23 | const pickerValue = attributes.options.indexOf(attributes.value).toString(); 24 | if (Platform.OS !== 'ios') { 25 | return ( 26 | 33 | 34 | {attributes.label} 35 | 36 | 37 | this.handleChange(value)} 44 | >{ 45 | attributes.options.map((item, index) => ( 46 | 47 | )) 48 | } 49 | 50 | 51 | 52 | 53 | ); 54 | } 55 | return ( 56 | 63 | this.panel.toggle()} 65 | style={{ 66 | flex: 1, 67 | flexDirection: 'row', 68 | justifyContent: 'space-between', 69 | alignItems: 'center', 70 | paddingVertical: 10, 71 | }} 72 | > 73 | 74 | {attributes.label} 75 | 76 | 77 | {isValueValid ? attributes.value : 'None'} 78 | 79 | 80 | 81 | 82 | { this.panel = c; }} 84 | > 85 | this.handleChange(value)} 91 | >{ 92 | attributes.options.map((item, index) => ( 93 | 94 | )) 95 | } 96 | 97 | 98 | 99 | 100 | 101 | ); 102 | } 103 | } -------------------------------------------------------------------------------- /src/fields/picker/styles.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bietkul/react-native-form-builder/05fce486f48adf8a43b3da25853a7b08fb12d851/src/fields/picker/styles.js -------------------------------------------------------------------------------- /src/fields/select/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react'; 3 | import { Modal, Dimensions } from 'react-native'; 4 | import { 5 | View, 6 | Text, 7 | Container, 8 | Header, 9 | Content, 10 | ListItem, 11 | CheckBox, 12 | Left, 13 | Right, 14 | Icon, 15 | Body, 16 | Title, 17 | Button, 18 | } from 'native-base'; 19 | 20 | const deviceWidth = Dimensions.get('window').width; 21 | 22 | export default class SelectField extends Component { 23 | static propTypes = { 24 | attributes: PropTypes.object, 25 | updateValue: PropTypes.func, 26 | theme: PropTypes.object, 27 | ErrorComponent: PropTypes.func, 28 | } 29 | constructor(props) { 30 | super(props); 31 | this.state = { 32 | modalVisible: false, 33 | }; 34 | } 35 | toggleModalVisible() { 36 | this.setState({ 37 | modalVisible: !this.state.modalVisible, 38 | }); 39 | } 40 | toggleSelect(value) { 41 | const attributes = this.props.attributes; 42 | const newSelected = attributes.multiple ? attributes.value : value; 43 | if (attributes.multiple) { 44 | const index = attributes.objectType ? newSelected.findIndex(option => 45 | option[attributes.primaryKey] === value[attributes.primaryKey] 46 | ) : newSelected.indexOf(value); 47 | if (index === -1) { 48 | newSelected.push(value); 49 | } else { 50 | newSelected.splice(index, 1); 51 | } 52 | } 53 | this.setState({ 54 | modalVisible: attributes.multiple ? this.state.modalVisible : false, 55 | }, () => this.props.updateValue(this.props.attributes.name, newSelected)); 56 | } 57 | render() { 58 | const { theme, attributes, ErrorComponent } = this.props; 59 | const selectedText = attributes.multiple ? 60 | attributes.value.length || 'None' : 61 | attributes.objectType ? 62 | (attributes.value && attributes.value[attributes.labelKey]) || 'None' 63 | : attributes.value || 'None'; 64 | return ( 65 | 66 | this.toggleModalVisible()}> 67 | 68 | {attributes.label} 69 | 70 | 71 | 72 | {selectedText} 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | this.toggleModalVisible()} 85 | > 86 | 87 |
88 | 89 | 95 | 96 | 97 | {attributes.label || 'Select'} 98 | 99 | 100 |
101 | 102 | { 103 | attributes.options.map((item, index) => { 104 | let isSelected = false; 105 | if (attributes.multiple) { 106 | isSelected = attributes.objectType ? 107 | attributes.value.findIndex(option => 108 | option[attributes.primaryKey] === item[attributes.primaryKey] 109 | ) !== -1 : (attributes.value.indexOf(item) !== -1); 110 | } 111 | return ( 112 | this.toggleSelect(item)} 115 | > 116 | { attributes.multiple && 117 | this.toggleSelect(item)} 119 | checked={isSelected} 120 | /> 121 | } 122 | 123 | 124 | {attributes.objectType ? item[attributes.labelKey] : item } 125 | 126 | 127 | ); 128 | }) 129 | } 130 | 131 |
132 |
133 |
134 | ); 135 | } 136 | } -------------------------------------------------------------------------------- /src/fields/select/styles.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bietkul/react-native-form-builder/05fce486f48adf8a43b3da25853a7b08fb12d851/src/fields/select/styles.js -------------------------------------------------------------------------------- /src/fields/switch/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react'; 3 | import { View, Text, Switch } from 'native-base'; 4 | 5 | export default class SwitchField extends Component { 6 | static propTypes = { 7 | attributes: PropTypes.object, 8 | theme: PropTypes.object, 9 | updateValue: PropTypes.func, 10 | ErrorComponent: PropTypes.func, 11 | } 12 | handleChange(value) { 13 | this.props.updateValue(this.props.attributes.name, value); 14 | } 15 | render() { 16 | const { attributes, theme, ErrorComponent } = this.props; 17 | return ( 18 | 19 | 33 | {attributes.label} 34 | this.handleChange(value)} 37 | value={attributes.value} 38 | /> 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | } 46 | } -------------------------------------------------------------------------------- /src/fields/switch/styles.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bietkul/react-native-form-builder/05fce486f48adf8a43b3da25853a7b08fb12d851/src/fields/switch/styles.js -------------------------------------------------------------------------------- /src/fields/textInput/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react'; 3 | import { View, Item, Input, Icon, ListItem, Text } from 'native-base'; 4 | import { Platform } from 'react-native'; 5 | import { getKeyboardType } from '../../utils/methods'; 6 | 7 | export default class TextInputField extends Component { 8 | static propTypes = { 9 | attributes: PropTypes.object, 10 | theme: PropTypes.object, 11 | updateValue: PropTypes.func, 12 | onSummitTextInput: PropTypes.func, 13 | ErrorComponent: PropTypes.func, 14 | } 15 | handleChange(text) { 16 | this.props.updateValue(this.props.attributes.name, text); 17 | } 18 | render() { 19 | const { theme, attributes, ErrorComponent } = this.props; 20 | const inputProps = attributes.props; 21 | const keyboardType = getKeyboardType(attributes.type); 22 | return ( 23 | 24 | 25 | 26 | 27 | { attributes.icon && 28 | 29 | } 30 | { this.textInput = c; }} 36 | underlineColorAndroid="transparent" 37 | numberOfLines={3} 38 | secureTextEntry={attributes.secureTextEntry || attributes.type === 'password'} 39 | placeholder={attributes.label} 40 | blurOnSubmit={false} 41 | onSubmitEditing={() => this.props.onSummitTextInput(this.props.attributes.name)} 42 | placeholderTextColor={theme.inputColorPlaceholder} 43 | editable={attributes.editable} 44 | value={attributes.value && attributes.value.toString()} 45 | keyboardType={keyboardType} 46 | onChangeText={text => this.handleChange(text)} 47 | {...inputProps} 48 | /> 49 | { theme.textInputErrorIcon && attributes.error ? 50 | : null} 51 | 52 | 53 | 54 | 55 | 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/fields/textInput/styles.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bietkul/react-native-form-builder/05fce486f48adf8a43b3da25853a7b08fb12d851/src/fields/textInput/styles.js -------------------------------------------------------------------------------- /src/formBuilder/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React, { Component } from 'react'; 3 | import { View, Keyboard, Text } from 'react-native'; 4 | import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'; 5 | import _ from 'lodash'; 6 | import TextInputField from '../fields/textInput'; 7 | import PickerField from '../fields/picker'; 8 | import SwitchField from '../fields/switch'; 9 | import DateField from '../fields/date'; 10 | import SelectField from '../fields/select'; 11 | import FormField from '../fields/form'; 12 | import baseTheme from '../theme'; 13 | import { autoValidate, getInitState, getDefaultValue, getResetValue } from '../utils/methods'; 14 | 15 | const DefaultErrorComponent = (props) => { 16 | const attributes = props.attributes; 17 | const theme = props.theme; 18 | if (attributes.error) { 19 | return ( 20 | 21 | { attributes.errorMsg } 22 | 23 | ); 24 | } 25 | return null; 26 | }; 27 | export default class FormBuilder extends Component { 28 | static propTypes = { 29 | fields: PropTypes.array, 30 | theme: PropTypes.object, 31 | scrollViewProps: PropTypes.object, 32 | customComponents: PropTypes.object, 33 | formData: PropTypes.object, 34 | errorComponent: PropTypes.func, 35 | autoValidation: PropTypes.bool, 36 | customValidation: PropTypes.func, 37 | onValueChange: PropTypes.func, 38 | } 39 | constructor(props) { 40 | super(props); 41 | const initialState = getInitState(props.fields); 42 | this.state = { 43 | fields: { 44 | ...initialState, 45 | }, 46 | errorStatus: false, 47 | }; 48 | // Supports Nested 49 | this.getValues = this.getValues.bind(this); 50 | // Invoked every time whenever any fields's value changes 51 | this.onValueChange = this.onValueChange.bind(this); 52 | // Generate fields 53 | this.generateFields = this.generateFields.bind(this); 54 | // forcefully set values for particular fields 55 | this.setValues = this.setValues.bind(this); 56 | // forcefully set default values for particular fields 57 | this.setToDefault = this.setToDefault.bind(this); 58 | // update the form fields 59 | this.updateState = this.updateState.bind(this); 60 | /* 61 | forcefully set errors for a particular field 62 | this.setErrors = this.setErrors.bind(this); 63 | reset errors to default for all fields 64 | this.resetErrors = this.resetErrors.bind(this); 65 | reset values to default for all fields 66 | this.resetValues = this.resetValues.bind(this); 67 | Currently Not needed but can be added in future 68 | */ 69 | // reset form values to default as well as errors 70 | this.resetForm = this.resetForm.bind(this); 71 | // Manages textInput Focus 72 | this.onSummitTextInput = this.onSummitTextInput.bind(this); 73 | } 74 | componentDidMount() { 75 | const { formData } = this.props; 76 | this.setValues(formData); 77 | } 78 | componentDidUpdate(prevProps) { 79 | if (!_.isEqual(prevProps.fields, this.props.fields)) { 80 | const nextState = this.updateState(this.props.fields); 81 | 82 | let fields = Object.assign({}, this.state.fields, nextState.fields); 83 | fields = _.omit(fields, nextState.hiddenFields); 84 | 85 | this.setState({ fields }); 86 | } 87 | } 88 | updateState(fields) { 89 | const newFields = {}; 90 | const hiddenFields = []; 91 | _.forEach(fields, (field) => { 92 | const fieldObj = field; 93 | if (!field.hidden && field.type) { 94 | const stateField = this.state.fields[field.name]; 95 | fieldObj.value = stateField && stateField.value ? stateField.value : getDefaultValue(field); 96 | newFields[field.name] = fieldObj; 97 | } else if (field.hidden) { 98 | hiddenFields.push(field.name); 99 | } 100 | }); 101 | return { fields: newFields, hiddenFields }; 102 | } 103 | onSummitTextInput(name) { 104 | const { fields } = this.state; 105 | const index = Object.keys(fields).indexOf(name); 106 | if (index !== -1 && this[Object.keys(fields)[index + 1]] 107 | && this[Object.keys(fields)[index + 1]].textInput) { 108 | this[Object.keys(fields)[index + 1]].textInput._root.focus(); 109 | } else { 110 | Keyboard.dismiss(); 111 | } 112 | } 113 | onValueChange(name, value) { 114 | const valueObj = this.state.fields[name]; 115 | if (valueObj) { 116 | valueObj.value = value; 117 | // Not Validate fields only when autoValidation prop is false 118 | if (this.props.autoValidation === undefined || this.props.autoValidation) { 119 | Object.assign(valueObj, autoValidate(valueObj)); 120 | } 121 | // Validate through customValidation if it is present in props 122 | if (this.props.customValidation 123 | && typeof this.props.customValidation === 'function') { 124 | Object.assign(valueObj, this.props.customValidation(valueObj)); 125 | } 126 | const newField = {}; 127 | newField[valueObj.name] = valueObj; 128 | // this.props.customValidation(valueObj); 129 | if (this.props.onValueChange && 130 | typeof this.props.onValueChange === 'function') { 131 | this.setState({ 132 | fields: { 133 | ...this.state.fields, 134 | ...newField, 135 | } 136 | }, () => this.props.onValueChange()); 137 | } else { 138 | this.setState({ 139 | fields: { 140 | ...this.state.fields, 141 | ...newField, 142 | } 143 | }); 144 | } 145 | } 146 | } 147 | // Returns the values of the fields 148 | getValues() { 149 | const values = {}; 150 | Object.keys(this.state.fields).forEach((fieldName) => { 151 | const field = this.state.fields[fieldName]; 152 | if (field) { 153 | values[field.name] = field.value; 154 | } 155 | }); 156 | return values; 157 | } 158 | // Helper function fro setToDefault 159 | getFieldDefaultValue(fieldObj) { 160 | const field = fieldObj; 161 | if (field.type === 'group') { 162 | const allFields = []; 163 | this.state.fields[field.name].fields.forEach((item) => { 164 | allFields.push(item.name); 165 | }); 166 | this[field.name].group.setToDefault(allFields); 167 | field.value = this[field.name].group.getValues(); 168 | } else { 169 | field.value = getDefaultValue(field); 170 | } 171 | return field; 172 | } 173 | /* Set particular field value to default SUPPORTS NESTED FORMS 174 | Required Form 175 | For multiple Fields 176 | [fieldName1, fieldName2, fieldName3 .....] 177 | For Single Field 178 | (fieldName1) 179 | */ 180 | setToDefault(...args) { 181 | if (args && args.length) { 182 | if (typeof args[0] === 'object') { 183 | const newFields = {}; 184 | args[0].forEach((item) => { 185 | const field = this.state.fields[item]; 186 | if (field) { 187 | field.value = getDefaultValue(field); 188 | newFields[field.name] = this.getFieldDefaultValue(field); 189 | } 190 | }); 191 | this.setState({ 192 | fields: { 193 | ...this.state.fields, 194 | ...newFields, 195 | } 196 | }); 197 | } else { 198 | const field = this.state.fields[args[0]]; 199 | if (field) { 200 | const newField = {}; 201 | newField[field.name] = this.getFieldDefaultValue(field); 202 | this.setState({ 203 | fields: { 204 | ...this.state.fields, 205 | ...newFields, 206 | } 207 | }); 208 | } 209 | } 210 | } 211 | } 212 | // Helper function for setValues 213 | getFieldValue(fieldObj, value) { 214 | const field = fieldObj; 215 | if (field.type === 'group') { 216 | const subFields = {}; 217 | Object.keys(value).forEach((fieldName) => { 218 | subFields[fieldName] = value[fieldName]; 219 | }); 220 | this[field.name].group.setValues(subFields); 221 | field.value = this[field.name].group.getValues(); 222 | // Remaing thing is error Handling Here 223 | } else { 224 | field.value = value; 225 | // also check for errors 226 | if (this.props.autoValidation === undefined || this.props.autoValidation) { 227 | Object.assign(field, autoValidate(field)); 228 | } 229 | // Validate through customValidation if it is present in props 230 | if (this.props.customValidation 231 | && typeof this.props.customValidation === 'function') { 232 | Object.assign(field, this.props.customValidation(field)); 233 | } 234 | } 235 | return field; 236 | } 237 | // Set Values 238 | // Params Format: 239 | // {name1: value1, name2: value2, ......} 240 | setValues(...args) { 241 | if (args && args.length && args[0]) { 242 | const newFields = {}; 243 | Object.keys(args[0]).forEach((fieldName) => { 244 | const field = this.state.fields[fieldName]; 245 | if (field) { 246 | newFields[field.name] = this.getFieldValue(field, args[0][fieldName]); 247 | } 248 | }); 249 | this.setState({ 250 | fields: { 251 | ...this.state.fields, 252 | ...newFields, 253 | } 254 | }); 255 | } 256 | } 257 | // Reset Form values & errors NESTED SUPPORTED 258 | resetForm() { 259 | const newFields = {}; 260 | Object.keys(this.state.fields).forEach((fieldName) => { 261 | const field = this.state.fields[fieldName]; 262 | if (field) { 263 | field.value = (field.editable !== undefined && !field.editable) ? 264 | getDefaultValue(field) : 265 | getResetValue(field); 266 | field.error = false; 267 | field.errorMsg = ''; 268 | if (field.type === 'group') { 269 | this[field.name].group.resetForm(); 270 | } 271 | newFields[field.name] = field; 272 | } 273 | }); 274 | this.setState({ 275 | fields: { 276 | ...this.state.fields, 277 | ...newFields, 278 | } 279 | }); 280 | } 281 | generateFields() { 282 | const theme = Object.assign(baseTheme, this.props.theme); 283 | const { customComponents, errorComponent, fields } = this.props; 284 | // Use fields from props to maintain the order of the props if the hidden prop is changed 285 | const renderFields = fields.map(({ name: fieldName }, index) => { 286 | const field = this.state.fields[fieldName]; 287 | if (field && !field.hidden) { 288 | const commonProps = { 289 | key: index, 290 | theme, 291 | attributes: this.state.fields[field.name], 292 | updateValue: this.onValueChange, 293 | ErrorComponent: errorComponent || DefaultErrorComponent, 294 | }; 295 | if (customComponents) { 296 | const CustomComponentObj = customComponents[field.type]; 297 | if (CustomComponentObj) { 298 | const CustomComponent = CustomComponentObj.component; 299 | const CustomComponentProps = CustomComponentObj.props; 300 | return ( 301 | { this[field.name] = c; }} 303 | {... commonProps} 304 | {...CustomComponentProps} 305 | onSummitTextInput={this.onSummitTextInput} 306 | /> 307 | ); 308 | } 309 | } 310 | switch (field.type) { 311 | case 'text': 312 | case 'email': 313 | case 'number': 314 | case 'url': 315 | case 'password': 316 | return ( 317 | { this[field.name] = c; }} 319 | {... commonProps} 320 | onSummitTextInput={this.onSummitTextInput} 321 | /> 322 | ); 323 | case 'picker': 324 | return ( 325 | { this[field.name] = c; }} 327 | {... commonProps} 328 | /> 329 | ); 330 | case 'select': 331 | return ( 332 | { this[field.name] = c; }} 334 | {... commonProps} 335 | /> 336 | ); 337 | case 'switch': 338 | return ( 339 | { this[field.name] = c; }} 341 | {... commonProps} 342 | /> 343 | ); 344 | case 'date': 345 | return ( 346 | { this[field.name] = c; }} 348 | {... commonProps} 349 | /> 350 | ); 351 | case 'group': 352 | return ( 353 | { this[field.name] = c; }} 355 | {... commonProps} 356 | {...this.props} 357 | /> 358 | ); 359 | default: 360 | return null; 361 | } 362 | } 363 | }); 364 | return renderFields; 365 | } 366 | render() { 367 | return ( 368 | 373 | 374 | {this.generateFields() || } 375 | 376 | 377 | 378 | ); 379 | } 380 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | 3 | 4 | import FormBuilder from './formBuilder'; 5 | 6 | export default FormBuilder; 7 | -------------------------------------------------------------------------------- /src/styles.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | pickerMainIOS: { 3 | marginHorizontal: 10, 4 | marginVertical: 0, 5 | marginLeft: 15, 6 | flex: 1, 7 | }, 8 | pickerMainAndroid: { 9 | marginHorizontal: 10, 10 | marginVertical: 0, 11 | marginLeft: 15, 12 | flexDirection: 'row', 13 | alignItems: 'center', 14 | flex: 1, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/theme.js: -------------------------------------------------------------------------------- 1 | import { Platform } from 'react-native'; 2 | 3 | export default { 4 | backgroundColor: '#5cb85c', 5 | textInputBgColor: 'black', 6 | pickerBgColor: 'transparent', 7 | textInputIconColor: '#000', 8 | pickerColorSelected: '#000', 9 | inputBorderColor: '#D9D5DC', 10 | borderWidth: 1, 11 | inputColorPlaceholder: '#ADADAD', 12 | inputColor: '#575757', 13 | inputFontSize: 15, 14 | labelActiveColor: '#575757', 15 | errorMsgColor: '#ed2f2f', 16 | changeTextInputColorOnError: true, 17 | textInputErrorIcon: 'close-circle', 18 | }; 19 | -------------------------------------------------------------------------------- /src/utils/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | fields: [ 3 | { 4 | type: 'text', 5 | name: 'user_name', 6 | icon: 'ios-person', 7 | iconOrientation: 'right', 8 | required: true, 9 | label: 'Username', 10 | editable: true, 11 | props: {}, /* If you want to add some extra props like autoCapitalize or 12 | autoFocus only available for textInput*/ 13 | }, 14 | { 15 | type: 'password', 16 | name: 'password', 17 | icon: 'ios-lock', 18 | required: true, 19 | label: 'Password', 20 | props: { 21 | secureTextEntry: true, 22 | }, 23 | }, 24 | { 25 | type: 'date', 26 | mode: 'date', // 'time', 'datetime' 27 | name: 'birthday', 28 | label: 'Birthday', 29 | maxDate: new Date(2010, 7, 1), 30 | }, 31 | { 32 | type: 'group', 33 | name: 'work_address', 34 | label: 'Address', 35 | fields: [ 36 | { 37 | type: 'text', 38 | name: 'city', 39 | label: 'City', 40 | // defaultValue: 'Bangalore', 41 | }, 42 | { 43 | type: 'text', 44 | name: 'country', 45 | label: 'Country', 46 | // defaultValue: 'India', 47 | }, 48 | ], 49 | }, 50 | { 51 | type: 'select', // required 52 | name: 'select', // required 53 | multiple: true, // default false 54 | required: true, // default false 55 | label: 'Select', // required 56 | labelKey: 'name', // tells the display key (required) 57 | primaryKey: 'id', // tells the unique key (required) 58 | objectType: true, // tells the type of values is object default false 59 | options: [ // required 60 | { 61 | id: 1, 62 | name: 'CAR', 63 | }, 64 | { 65 | id: 2, 66 | name: 'BIKE', 67 | }, 68 | { 69 | id: 3, 70 | name: 'BICYCLE', 71 | }, 72 | ], 73 | // options: ['CAR', 'BIKE', 'BICYCLE'], 74 | defaultValue: [{ // In case of multiple it will be an array otherwise it will be an object 75 | id: 3, 76 | name: 'BICYCLE', 77 | }], 78 | // defaultValue: ['CAR', 'BIKE'], 79 | }, 80 | { 81 | type: 'select', // required 82 | name: 'status', // required 83 | label: 'Status', // required 84 | options: ['In Meeting', 'Busy', 'Happy', 'Sad'], 85 | defaultValue: ['Happy'], 86 | }, 87 | { 88 | type: 'switch', 89 | name: 'switch', 90 | label: 'Notify Me', 91 | defaultValue: true, 92 | }, 93 | { 94 | type: 'text', 95 | name: 'description', 96 | label: 'Describe Yourself', 97 | required: true, 98 | props: { 99 | multiline: true, 100 | numberOfLines: 3, 101 | }, 102 | }, 103 | { 104 | type: 'email', 105 | name: 'email', 106 | required: true, 107 | label: 'Email', 108 | // defaultValue: 'www.jiji@jij.com', 109 | }, 110 | { 111 | type: 'number', 112 | name: 'number', 113 | required: true, 114 | label: 'Age', 115 | // defaultValue: 18, 116 | }, 117 | { 118 | type: 'url', 119 | name: 'url', 120 | required: true, 121 | label: 'URL', 122 | // defaultValue: 'www.github.com', 123 | }, 124 | ], 125 | }; 126 | -------------------------------------------------------------------------------- /src/utils/methods.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import { isEmail, isEmpty } from './validators'; 3 | 4 | export function getKeyboardType(textType) { 5 | switch (textType) { 6 | case 'email': 7 | return 'email-address'; 8 | case 'number': 9 | return 'numeric'; 10 | default: 11 | return 'default'; 12 | } 13 | } 14 | export function autoValidate(field) { 15 | let error = false; 16 | let errorMsg = ''; 17 | if (field.required) { 18 | switch (field.type) { 19 | case 'email': 20 | if (isEmpty(field.value)) { 21 | error = true; 22 | errorMsg = `${field.label} is required`; 23 | } else if (!isEmail(field.value)) { 24 | error = true; 25 | errorMsg = 'Please enter a valid email'; 26 | } 27 | break; 28 | case 'text': 29 | case 'url': 30 | case 'password': 31 | if (isEmpty(field.value)) { 32 | error = true; 33 | errorMsg = `${field.label} is required`; 34 | } 35 | break; 36 | case 'number': 37 | if (field.type === 'number') { 38 | if (isEmpty(field.value)) { 39 | error = true; 40 | errorMsg = `${field.label} is required`; 41 | } else if (isNaN(field.value)) { 42 | errorMsg = `${field.label} should be a number`; 43 | } 44 | } 45 | break; 46 | default: 47 | } 48 | } 49 | return { error, errorMsg }; 50 | } 51 | export function getDefaultValue(field) { 52 | switch (field.type) { 53 | case 'text': 54 | case 'number': 55 | case 'email': 56 | case 'password': 57 | case 'url': 58 | return field.defaultValue || ''; 59 | case 'picker': { 60 | if ((field.options).indexOf(field.defaultValue) !== -1) { 61 | return field.defaultValue; 62 | } 63 | return field.options[0]; 64 | } 65 | case 'select': { 66 | if (Array.isArray(field.defaultValue)) { 67 | const selected = []; 68 | if (!field.objectType) { 69 | field.defaultValue.forEach((item) => { 70 | if ((field.options).indexOf(item) !== -1) { 71 | selected.push(item); 72 | } 73 | }); 74 | } else { 75 | field.defaultValue.forEach((item) => { 76 | if ((field.options).findIndex(option => 77 | option[field.primaryKey] === item[field.primaryKey] 78 | ) !== -1) { 79 | selected.push(item); 80 | } 81 | }); 82 | } 83 | return selected; 84 | } 85 | if (!field.multiple) { 86 | return field.defaultValue || null; 87 | } 88 | return []; 89 | } 90 | case 'switch': 91 | if (typeof field.defaultValue === 'boolean') { 92 | return field.defaultValue; 93 | } 94 | return false; 95 | case 'date': 96 | { const dateDefaultValue = field.defaultValue && new Date(field.defaultValue); 97 | if (dateDefaultValue && !_.isNaN(dateDefaultValue.getTime())) { 98 | return dateDefaultValue; 99 | } 100 | return null; } 101 | case 'group': 102 | if (field.fields) { 103 | return field.defaultValue; 104 | } 105 | return null; 106 | default: 107 | return null; 108 | } 109 | } 110 | export function getResetValue(field) { 111 | switch (field.type) { 112 | case 'text': 113 | case 'number': 114 | case 'email': 115 | case 'password': 116 | case 'url': 117 | return ''; 118 | case 'picker': 119 | return field.options[0]; 120 | case 'select': 121 | return field.multiple ? [] : null; 122 | case 'switch': 123 | return false; 124 | case 'date': 125 | return null; 126 | default: 127 | return null; 128 | } 129 | } 130 | export function getInitState(fields) { 131 | const state = {}; 132 | _.forEach(fields, (field) => { 133 | const fieldObj = field; 134 | fieldObj.error = false; 135 | fieldObj.errorMsg = ''; 136 | if (!field.hidden && field.type) { 137 | fieldObj.value = getDefaultValue(field); 138 | state[field.name] = fieldObj; 139 | } 140 | }); 141 | return state; 142 | } 143 | -------------------------------------------------------------------------------- /src/utils/validators.js: -------------------------------------------------------------------------------- 1 | export function isEmpty(value) { 2 | return value.trim() === ''; 3 | } 4 | export function isEmail(value) { 5 | const emailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; 6 | return emailRegex.test(value); 7 | } 8 | --------------------------------------------------------------------------------