Skip to content

Instantly share code, notes, and snippets.

@insin
Last active October 10, 2016 23:12
Show Gist options
  • Save insin/7a08958a71e7443d658564f15fd0191a to your computer and use it in GitHub Desktop.
Save insin/7a08958a71e7443d658564f15fd0191a to your computer and use it in GitHub Desktop.
React Form Example / Live version: http://react-form-example.surge.sh/

To clone this example and run a development server:

git clone https://gist.github.com/7a08958a71e7443d658564f15fd0191a.git react-form-example
cd react-form-example
npm install
npm start

To create a static build:

npm run build

This example uses nwb for build tooling - nwb's react run and react build commands allow you to develop quick prototypes without having to set up a project structure.

import React from 'react'
import {
Alert,
Button,
Col, ControlLabel,
Form, FormControl, FormGroup,
HelpBlock,
} from 'react-bootstrap'
import {APPS, APPS_BY_RELEASE} from './metadata'
import SelectApps from './SelectApps'
const EMR_RELEASES = Object.keys(APPS_BY_RELEASE)
function validate(values) {
let errors = {}
if (values.applications.length === 0) {
errors.applications = 'Please select some applications to install.'
}
return errors
}
let FormExample = React.createClass({
getInitialState() {
return {
errors: {},
touched: {
applications: false,
},
values: {
applications: ['hue', 'hive', 'pig', 'spark', 'hadoop'],
releaseLabel: '',
},
}
},
/**
* Update selected application state.
*/
handleApplicationChange(applications) {
let values = {...this.state.values, applications}
let stateChange = {errors: validate(values), values}
if (!this.state.touched.applications) {
stateChange.touched = {...this.state.touched, applications: true}
}
this.setState(stateChange)
},
/**
* Correct selected applications when the release label is changed.
*/
handleReleaseLabelChange(e) {
let {value: releaseLabel} = e.target
// If the release label has been cleared, don't do anything else
if (releaseLabel === '') {
return this.setState({values: {...this.state.values, releaseLabel}})
}
let {applications} = this.state.values
let nextAppIds = Object.keys(APPS_BY_RELEASE[releaseLabel])
let values = {
...this.state.values,
// Deselect any selected apps which no longer apply
applications: applications.filter(appId => nextAppIds.indexOf(appId) !== -1),
releaseLabel,
}
this.setState({errors: validate(values), values})
},
/**
* Generic onChange handler.
*/
handleChange(e) {
let input = e.target
this.setState({
values: {
...this.state.values,
[input.name]: input.type === 'checkbox' ? input.checked : input.value,
}
})
},
handleSubmit(e) {
e.preventDefault()
let errors = validate(this.state.values)
if (Object.keys(errors).length > 0) {
return this.setState({errors})
}
// Create a cluster model object for submission
let {applications, ...cluster} = this.state.values
let isV4 = /^emr-4/.test(cluster.releaseLabel)
// Convert application ids to names for submission
cluster.applications = applications.map(appId => {
let {name, v4Sandbox} = APPS[appId]
// Certain apps need a '-Sandbox' suffix in EMR v4
return `${name}${isV4 && v4Sandbox ? '-Sandbox' : ''}`
})
console.log(JSON.stringify(cluster, null, 2))
},
render() {
let {errors, touched, values} = this.state
let releaseSelected = values.releaseLabel !== ''
return <Form onSubmit={this.handleSubmit} horizontal>
<legend>React Form Example</legend>
<FormGroup controlId="releaseLabel">
<Col componentClass={ControlLabel} sm={3} md={2}>Release Label:</Col>
<Col sm={9} md={6}>
<FormControl required componentClass="select" name="releaseLabel"
value={values.releaseLabel} onChange={this.handleReleaseLabelChange}
>
<option value=""></option>
{EMR_RELEASES.map(version =>
<option key={version} value={version}>{version}</option>
)}
</FormControl>
<HelpBlock>
The identifier for the EMR release, which includes a set of software,
to use with Amazon EC2 instances that are part of an Amazon EMR cluster.
See <a href="http://docs.aws.amazon.com/ElasticMapReduce/latest/ReleaseGuide/emr-release-components.html" target="_blank">About Amazon EMR Releases</a>.
</HelpBlock>
</Col>
</FormGroup>
<FormGroup>
<Col componentClass={ControlLabel} sm={3} md={2}>Applications:</Col>
<Col sm={9} md={6}>
{!releaseSelected && <HelpBlock>Select a Release Label to show available applications.</HelpBlock>}
{releaseSelected && <div>
<SelectApps
onChange={this.handleApplicationChange}
release={values.releaseLabel}
value={values.applications}
/>
{errors.applications && touched.applications
? <Alert bsStyle="danger">{errors.applications}</Alert>
: <HelpBlock>Select applications to install on your cluster.</HelpBlock>
}
</div>}
</Col>
</FormGroup>
<FormGroup>
<Col smOffset={3} sm={9} mdOffset={2} md={6}>
<hr/>
<Button bsStyle="primary" type="submit">Create Cluster</Button>
</Col>
</FormGroup>
</Form>
}
})
export default FormExample
import 'bootstrap/dist/css/bootstrap.css'
import React from 'react'
import {Grid} from 'react-bootstrap'
import {render} from 'react-dom'
import FormExample from './FormExample'
render(
<Grid>
<FormExample/>
</Grid>,
document.querySelector('#app')
)
/**
* Application names match the names expected by the EMR API.
*
* Sandbox apps must have a '-Sandbox' suffix added when submitting application
* names to the API for EMR v4.
*/
export const APPS = {
hadoop: {
name: 'Hadoop',
url: 'http://ganglia.info/',
},
ganglia: {
name: 'Ganglia',
url: 'http://hadoop.apache.org/docs/current/',
},
hbase: {
name: 'HBase',
url: 'http://hbase.apache.org/',
},
hive: {
name: 'Hive',
url: 'https://cwiki.apache.org/confluence/display/Hive',
},
hcatalog: {
name: 'HCatalog',
url: 'https://cwiki.apache.org/confluence/display/Hive/HCatalog',
},
hue: {
name: 'Hue',
url: 'http://gethue.com/',
},
mahout: {
name: 'Mahout',
url: 'http://mahout.apache.org/',
},
oozie: {
name: 'Oozie',
v4Sandbox: true,
url: 'http://oozie.apache.org/',
},
phoenix: {
name: 'Phoenix',
url: 'https://phoenix.apache.org/',
},
pig: {
name: 'Pig',
url: 'http://pig.apache.org/',
},
presto: {
name: 'Presto',
v4Sandbox: true,
url: 'https://prestodb.io/',
},
spark: {
name: 'Spark',
url: 'https://spark.apache.org/docs/latest/',
},
sqoop: {
name: 'Sqoop',
v4Sandbox: true,
url: 'http://sqoop.apache.org/',
},
tez: {
name: 'Tez',
url: 'https://tez.apache.org/',
},
zeppelin: {
name: 'Zeppelin',
v4Sandbox: true,
url: 'https://zeppelin.incubator.apache.org/',
},
zookeeper: {
name: 'ZooKeeper',
v4Sandbox: true,
url: 'https://zookeeper.apache.org/',
},
}
/**
* Applications and versions available in each EMR release.
*/
export const APPS_BY_RELEASE = {
'emr-4.2.0': {
hadoop: '2.6.0',
ganglia: '3.6.0',
hive: '1.0.0',
hcatalog: '1.0.0',
hue: '3.7.1',
mahout: '0.11.0',
oozie: '4.2.0',
pig: '0.14.0',
presto: '0.125',
spark: '1.5.2',
zeppelin: '0.5.5',
},
'emr-4.3.0': {
hadoop: '2.7.1',
ganglia: '3.7.2',
hive: '1.0.0',
hcatalog: '1.0.0',
hue: '3.7.1',
mahout: '0.11.0',
oozie: '4.2.0',
pig: '0.14.0',
presto: '0.130',
spark: '1.6.0',
zeppelin: '0.5.5',
},
'emr-4.4.0': {
hadoop: '2.7.1',
ganglia: '3.7.2',
hive: '1.0.0',
hcatalog: '1.0.0',
hue: '3.7.1',
mahout: '0.11.1',
oozie: '4.2.0',
pig: '0.14.0',
presto: '0.136',
spark: '1.6.0',
sqoop: '1.4.6',
zeppelin: '0.5.6',
},
'emr-4.5.0': {
hadoop: '2.7.2',
ganglia: '3.7.2',
hive: '1.0.0',
hcatalog: '1.0.0',
hue: '3.7.1',
mahout: '0.11.1',
oozie: '4.2.0',
pig: '0.14.0',
presto: '0.140',
spark: '1.6.1',
sqoop: '1.4.6',
zeppelin: '0.5.6',
},
'emr-4.6.0': {
hadoop: '2.7.2',
ganglia: '3.7.2',
hbase: '1.2.0',
hive: '1.0.0',
hcatalog: '1.0.0',
hue: '3.7.1',
mahout: '0.11.1',
oozie: '4.2.0',
pig: '0.14.0',
presto: '0.143',
spark: '1.6.1',
sqoop: '1.4.6',
zeppelin: '0.5.6',
zookeeper: '3.4.8',
},
'emr-4.7.1': {
hadoop: '2.7.2',
ganglia: '3.7.2',
hbase: '1.2.1',
hive: '1.0.0',
hcatalog: '1.0.0',
hue: '3.7.1',
mahout: '0.12.0',
oozie: '4.2.0',
phoenix: '4.7.0',
pig: '0.14.0',
presto: '0.147',
spark: '1.6.1',
sqoop: '1.4.6',
tez: '0.8.3',
zeppelin: '0.5.6',
zookeeper: '3.4.8',
},
'emr-4.7.2': {
hadoop: '2.7.2',
ganglia: '3.7.2',
hbase: '1.2.1',
hive: '1.0.0',
hcatalog: '1.0.0',
hue: '3.7.1',
mahout: '0.12.2',
oozie: '4.2.0',
phoenix: '4.7.0',
pig: '0.14.0',
presto: '0.148',
spark: '1.6.2',
sqoop: '1.4.6',
tez: '0.8.3',
zeppelin: '0.5.6',
zookeeper: '3.4.8',
},
'emr-5.0.0': {
hadoop: '2.7.2',
ganglia: '3.7.2',
hbase: '1.2.2',
hive: '2.1.0',
hcatalog: '2.1.0',
hue: '3.10.0',
mahout: '0.12.2',
oozie: '4.2.0',
phoenix: '4.7.0',
pig: '0.16.0',
presto: '0.150',
spark: '2.0.0',
sqoop: '1.4.6',
tez: '0.8.4',
zeppelin: '0.6.1',
zookeeper: '3.4.8',
},
}
module.exports = {
polyfill: false,
babel: {
cherryPick: 'react-bootstrap'
}
}
{
"name": "react-form-example",
"scripts": {
"build": "react build index.js --title \"React Form Example\"",
"lint": "eslint *.js",
"start": "react run index.js"
},
"dependencies": {
"bootstrap": "3.3.7",
"react": "15.3.2",
"react-bootstrap": "0.30.5",
"react-dom": "15.3.2",
"react-fa": "4.1.2"
},
"devDependencies": {
"eslint-config-jonnybuchanan": "4.6.0",
"nwb": "0.12.1"
}
}
import React, {PropTypes as t} from 'react'
import {Table} from 'react-bootstrap'
import Icon from 'react-fa'
import {APPS, APPS_BY_RELEASE} from './metadata'
let SelectApps = React.createClass({
propTypes: {
disabled: t.bool,
onChange: t.func.isRequired,
release: t.string.isRequired,
value: t.array.isRequired,
},
handleToggleAll(e) {
this.props.onChange(
e.target.checked
? Object.keys(APPS_BY_RELEASE[this.props.release])
: []
)
},
handleToggleApp(e) {
let appIds = this.props.value.slice()
if (e.target.checked) {
appIds.push(e.target.value)
}
else {
appIds.splice(appIds.indexOf(e.target.value), 1)
}
this.props.onChange(appIds)
},
render() {
let {disabled, release, value} = this.props
let versions = APPS_BY_RELEASE[release]
let isV4 = /^emr-4/.test(release)
let appIds = Object.keys(APPS).filter(id => id in versions)
return <Table bordered striped condensed>
<colgroup>
<col width="1%"/>
<col width="49%"/>
<col width="49%"/>
<col width="1%"/>
</colgroup>
<thead>
<tr>
<th>
<input
checked={value.length === appIds.length}
disabled={disabled}
onChange={this.handleToggleAll}
type="checkbox"
/>
</th>
<th>Application</th>
<th>Version</th>
<th>Info</th>
</tr>
</thead>
<tbody>
{appIds.map(appId => {
let checked = value.indexOf(appId) !== -1
let controlId = `application-${appId}`
let version = versions[appId]
let {name, url, v4Sandbox} = APPS[appId]
return <tr key={appId}>
<td>
<input
checked={checked}
disabled={disabled}
id={controlId}
onChange={this.handleToggleApp}
type="checkbox"
value={appId}
/>
</td>
<td>
<label style={{fontWeight: 'normal'}} htmlFor={controlId}>
{`${name}${isV4 && v4Sandbox ? ' (Sandbox)' : ''}`}
</label>
</td>
<td>{version}</td>
<td style={{whiteSpace: 'nowrap'}}>
<a href={url} target="_blank" title={`Find out more about ${name}`}>
<Icon name="info-circle"/> <Icon name="external-link"/>
</a>
</td>
</tr>
})}
</tbody>
</Table>
}
})
export default SelectApps
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment