Commit 07ce8d1c9520040bec94f1012e9ab63d690c18f4

Authored by Rafael Arenas Schuchowsky
1 parent 667ddb29bc
Exists in master

New student form layout and inital controls

imports/client/views/core/ErrorLabel.js
... ... @@ -0,0 +1,17 @@
  1 +import React, { PropTypes } from 'react'
  2 +
  3 +const ErrorLabel = props => (
  4 + <label
  5 + className="validation-error-label"
  6 + htmlFor={props.field}
  7 + >
  8 + {props.children}
  9 + </label>
  10 +)
  11 +
  12 +ErrorLabel.propTypes = {
  13 + field: PropTypes.string.isRequired,
  14 + children: PropTypes.string.isRequired,
  15 +}
  16 +
  17 +export default ErrorLabel
... ...
imports/client/views/core/Form.js
... ... @@ -0,0 +1,144 @@
  1 +import React, { Component, PropTypes } from 'react'
  2 +import isEqual from 'lodash/isEqual'
  3 +import get from 'lodash/get'
  4 +import cloneDeep from 'lodash/cloneDeep'
  5 +import set from 'lodash/set'
  6 +
  7 +class Form extends Component {
  8 + constructor(props) {
  9 + super(props)
  10 + this.state = {
  11 + values: props.initialValues || {},
  12 + dirtyValues: [],
  13 + submitted: false
  14 + }
  15 + this.isPristine = this.isPristine.bind(this)
  16 + this.isDirty = this.isDirty.bind(this)
  17 + this.isSubmitted = this.isSubmitted.bind(this)
  18 + this.getValue = this.getValue.bind(this)
  19 + this.setValue = this.setValue.bind(this)
  20 + this.setPristine = this.setPristine.bind(this)
  21 + this.setDirty = this.setDirty.bind(this)
  22 + }
  23 +
  24 + componentWillReceiveProps(nextProps) {
  25 + if (!isEqual(this.props.initialValues, nextProps.initialValues)) {
  26 + this.setState(prevState => ({
  27 + values: { ...nextProps.initialValues, ...prevState.values }
  28 + }))
  29 + }
  30 + }
  31 +
  32 + isPristine(path) {
  33 + if (path) {
  34 + return !this.state.dirtyValues.find(dirtyValue => dirtyValue === path)
  35 + }
  36 + return !this.state.dirtyValues.length
  37 + }
  38 +
  39 + isDirty(path) {
  40 + return !this.isPristine(path)
  41 + }
  42 +
  43 + isSubmitted() {
  44 + return this.state.submitted
  45 + }
  46 +
  47 + getValue(path) {
  48 + if (!path) {
  49 + throw new Error(`getValue() requires a path`)
  50 + }
  51 + return get(this.state.values, path, '')
  52 + }
  53 +
  54 + setValue (path, value) {
  55 + if (!path) {
  56 + throw new Error(`setValue() requires a path`)
  57 + }
  58 + if (value === undefined) {
  59 + throw new Error(`setValue() requires a non-undefined value`)
  60 + }
  61 + this.setState(prevState => {
  62 + const prevValues = prevState.values
  63 + // Lo-Dash's set() mutates the original value, so we need to make a copy
  64 + const prevValuesCopy = cloneDeep(prevValues)
  65 + const nextValues = set(prevValuesCopy, path, value)
  66 + return { values: nextValues }
  67 + })
  68 + this.setDirty(path)
  69 + }
  70 +
  71 + setPristine(path) {
  72 + if (!path) {
  73 + throw new Error(`setPristine() requires a path`)
  74 + }
  75 + this.setState(prevState => ({
  76 + dirtyValues: (
  77 + this.isPristine(path)
  78 + ? prevState.dirtyValues
  79 + : prevState.dirtyValues.filter(dirtyValue => dirtyValue !== path)
  80 + )
  81 + }))
  82 + }
  83 +
  84 + setDirty(path) {
  85 + if (!path) {
  86 + throw new Error(`setDirty() requires a path`)
  87 + }
  88 + this.setState(prevState => ({
  89 + dirtyValues: (
  90 + this.isDirty(path)
  91 + ? prevState.dirtyValues
  92 + : [...prevState.dirtyValues, path]
  93 + )
  94 + }))
  95 + }
  96 +
  97 + reset() {
  98 + this.setState({
  99 + values: this.props.initialValues,
  100 + dirtyValues: [],
  101 + submitted: false
  102 + })
  103 + }
  104 +
  105 + handleSubmit(event) {
  106 + if (event) {
  107 + event.preventDefault()
  108 + }
  109 + this.setState({ submitted: true })
  110 + if (this.props.onSubmit) {
  111 + this.props.onSubmit(this.state.values)
  112 + }
  113 + }
  114 +
  115 + render() {
  116 + // eslint-disable-next-line
  117 + const { initialValues, children, ...rest } = this.props
  118 + return (
  119 + <form {...rest} onSubmit={this.handleSubmit}>
  120 + {children({
  121 + values: this.state.values,
  122 + isPristine: this.isPristine,
  123 + isDirty: this.isDirty,
  124 + isSubmitted: this.isSubmitted,
  125 + getValue: this.getValue,
  126 + setValue: this.setValue,
  127 + setPristine: this.setPristine,
  128 + setDirty: this.setDirty,
  129 + reset: this.reset
  130 + })}
  131 + </form>
  132 + )
  133 + }
  134 +}
  135 +
  136 +Form.propTypes = {
  137 + initialValues: PropTypes.object,
  138 + onSubmit: PropTypes.func,
  139 + children: PropTypes.func.isRequired
  140 +}
  141 +
  142 +Form.defaultProps = { initialValues: {} }
  143 +
  144 +export default Form
... ...
imports/client/views/core/Label.js
... ... @@ -0,0 +1,17 @@
  1 +import React, { PropTypes } from 'react'
  2 +
  3 +const Label = props => (
  4 + <label>
  5 + {props.children}
  6 + {props.required && (
  7 + <span className="text-danger">*</span>
  8 + )}
  9 + </label>
  10 +)
  11 +
  12 +Label.propTypes = {
  13 + children: PropTypes.string.isRequired,
  14 + required: PropTypes.bool,
  15 +}
  16 +
  17 +export default Label
... ...
imports/client/views/core/Stepper.js
... ... @@ -0,0 +1,21 @@
  1 +import React, { PropTypes } from 'react'
  2 +
  3 +const Stepper = props => (
  4 + <ul className="stepy-header">
  5 + {props.steps.map((step, index) => (
  6 + <li key={step.label} className={step.active && 'stepy-active'}>
  7 + <div>{index + 1}</div>
  8 + {step.label}
  9 + </li>
  10 + ))}
  11 + </ul>
  12 +)
  13 +
  14 +Stepper.propTypes = {
  15 + steps: PropTypes.arrayOf(PropTypes.shape({
  16 + label: PropTypes.string.isRequired,
  17 + active: PropTypes.bool.isRequired,
  18 + }))
  19 +}
  20 +
  21 +export default Stepper
... ...
imports/client/views/core/Validator.js
... ... @@ -0,0 +1,48 @@
  1 +import { Component, PropTypes } from 'react'
  2 +import isArray from 'lodash/isArray'
  3 +import get from 'lodash/get'
  4 +
  5 +class Validator extends Component {
  6 + constructor(props) {
  7 + super(props)
  8 + this.getErrors = this.getErrors.bind(this)
  9 + }
  10 +
  11 + getErrors() {
  12 + if (!this.props.validations) {
  13 + return null
  14 + }
  15 + const errors = Object.keys(this.props.validations).reduce((result, path) => {
  16 + const validations = this.props.validations[path]
  17 + if (!isArray(validations)) {
  18 + throw new Error(`validations[${path}] should be an array`)
  19 + }
  20 + return {
  21 + ...result,
  22 + [path]: validations.map((validation, index) => {
  23 + if (typeof validation !== 'function') {
  24 + throw new Error(`validations[${path}][${index}] should be a function`)
  25 + }
  26 + const error = validation(get(this.props.values, path, ''), this.props.values, path)
  27 + if (error && typeof error !== 'string') {
  28 + throw new Error(`validations[${path}][${index}] should return a string`)
  29 + }
  30 + return error
  31 + }).find(error => error) || null
  32 + }
  33 + }, {})
  34 + return Object.keys(errors).find(path => errors[path]) ? errors : null
  35 + }
  36 +
  37 + render() {
  38 + return this.props.children({ errors: this.getErrors() })
  39 + }
  40 +}
  41 +
  42 +Validator.propTypes = {
  43 + validations: PropTypes.object,
  44 + values: PropTypes.object,
  45 + children: PropTypes.func.isRequired
  46 +}
  47 +
  48 +export default Validator
... ...
imports/client/views/core/validations.js
... ... @@ -0,0 +1,10 @@
  1 +export const isRequired = value => !value && 'Required'
  2 +
  3 +export const minLength = minLength => {
  4 + return value => value && value.length < minLength && `Min. length: ${minLength}`
  5 +}
  6 +
  7 +export const isValidEmail = value => {
  8 + const exR = /^(([^<>()\[\]\\.,;:\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,}))$/
  9 + return !exR.test(value) && 'Please use a valid email'
  10 +}
... ...
imports/client/views/org/admin/students/AddStudentFormContainer.js
... ... @@ -0,0 +1,56 @@
  1 +import React, { Component } from 'react'
  2 +import { AddStudentForm } from './addStudentForm'
  3 +import StudentForm from './StudentForm'
  4 +import Form from '../../../core/Form'
  5 +import Validator from '../../../core/Validator'
  6 +import { isRequired } from '../../../core/validations'
  7 +
  8 +export class AddStudentFormContainer extends Component {
  9 +
  10 + constructor(props) {
  11 + super(props)
  12 + this.state = { currentStep: 0 }
  13 + this.handleNextClick = this.handleNextClick.bind(this)
  14 + this.handleBackClick = this.handleBackClick.bind(this)
  15 + }
  16 +
  17 + handleNextClick() {
  18 + this.form.handleSubmit()
  19 + if (this.validator.getErrors() && Object.keys(this.validator.getErrors()).length > 0) return
  20 + this.setState({ currentStep: this.state.currentStep + 1 })
  21 + this.form.reset()
  22 + }
  23 +
  24 + handleBackClick() {
  25 + this.setState({ currentStep: this.state.currentStep + -1 })
  26 + }
  27 +
  28 + render() {
  29 + return (
  30 + <Form onSubmit={() => alert('submitted')} ref={form => this.form = form}>
  31 + {({ values, setValue, getValue, isSubmitted, isDirty }) => (
  32 + <Validator
  33 + values={values}
  34 + ref={validator => this.validator = validator}
  35 + validations={{
  36 + admissionId: [isRequired],
  37 + }}
  38 + >
  39 + {({ errors }) => (
  40 + <StudentForm
  41 + isDirty={isDirty}
  42 + setValue={setValue}
  43 + getValue={getValue}
  44 + isSubmitted={isSubmitted}
  45 + errors={errors}
  46 + onNextClick={this.handleNextClick}
  47 + onBackClick={this.handleBackClick}
  48 + currentStep={this.state.currentStep}
  49 + />
  50 + )}
  51 + </Validator>
  52 + )}
  53 + </Form>
  54 + )
  55 + }
  56 +}
... ...
imports/client/views/org/admin/students/StudentForm.js
... ... @@ -0,0 +1,271 @@
  1 +import React, { PropTypes } from 'react'
  2 +import {
  3 + Row,
  4 + Col,
  5 + FormGroup,
  6 + FormControl,
  7 + Button
  8 +} from 'react-bootstrap'
  9 +import DatePicker from 'react-bootstrap-date-picker'
  10 +import Label from '../../../core/Label'
  11 +import Stepper from '../../../core/Stepper'
  12 +import ErrorLabel from '../../../core/ErrorLabel'
  13 +
  14 +const StudentForm = props => (
  15 + <div className="stepy-validation">
  16 + <Stepper
  17 + steps={[
  18 + {
  19 + label: 'Personal data',
  20 + active: props.currentStep === 0,
  21 + },
  22 + {
  23 + label: 'Address',
  24 + active: props.currentStep === 1,
  25 + },
  26 + {
  27 + label: 'Parent info',
  28 + active: props.currentStep === 2,
  29 + }
  30 + ]}
  31 + />
  32 + {props.currentStep === 0 && (
  33 + <fieldset title="1">
  34 + <legend className="text-semibold">Personal data</legend>
  35 + <Row>
  36 + <Col xs={12} sm={6}>
  37 + <FormGroup controlId="admissionId">
  38 + <Label required>Admission Id</Label>
  39 + <FormControl
  40 + type="text"
  41 + value={props.getValue('admissionId')}
  42 + placeholder="admission Id"
  43 + onChange={e => props.setValue('admissionId', e.target.value)}
  44 + />
  45 + {props.isSubmitted() && props.errors && props.errors.admissionId && (
  46 + <ErrorLabel> {props.errors.admissionId} </ErrorLabel>
  47 + )}
  48 + </FormGroup>
  49 + </Col>
  50 + <Col xs={12} sm={6}>
  51 + <FormGroup controlId="firstName">
  52 + <Label>First Name</Label>
  53 + <FormControl
  54 + type="text"
  55 + value={props.getValue('firstName')}
  56 + placeholder="First Name"
  57 + onChange={e => props.setValue('firstName', e.target.value)}
  58 + />
  59 + </FormGroup>
  60 + </Col>
  61 + </Row>
  62 + <Row>
  63 + <Col xs={12} sm={6}>
  64 + <FormGroup controlId="middleName">
  65 + <Label>Middle Name</Label>
  66 + <FormControl
  67 + type="text"
  68 + value={props.getValue('middleName')}
  69 + placeholder="Middle Name"
  70 + onChange={e => props.setValue('middleName', e.target.value)}
  71 + />
  72 + </FormGroup>
  73 + </Col>
  74 + <Col xs={12} sm={6}>
  75 + <FormGroup controlId="lastName">
  76 + <Label>Last Name</Label>
  77 + <FormControl
  78 + type="text"
  79 + value={props.getValue('lastName')}
  80 + placeholder="Last Name"
  81 + onChange={e => props.setValue('lastName', e.target.value)}
  82 + />
  83 + </FormGroup>
  84 + </Col>
  85 + </Row>
  86 + <Row>
  87 + <Col xs={12} sm={6}>
  88 + <FormGroup controlId="email">
  89 + <Label>Email</Label>
  90 + <FormControl
  91 + type="email"
  92 + value={props.getValue('email')}
  93 + placeholder="Email"
  94 + onChange={e => props.setValue('email', e.target.value)}
  95 + />
  96 + </FormGroup>
  97 + </Col>
  98 + <Col xs={12} sm={6}>
  99 + <FormGroup>
  100 + <Label>Date of birth</Label>
  101 + <DatePicker
  102 + id="dob"
  103 + value={props.getValue('dob')}
  104 + onChange={value => props.setValue('dob', value)}
  105 + />
  106 + </FormGroup>
  107 + </Col>
  108 + </Row>
  109 + <Row>
  110 + <Col xs={12} sm={6}>
  111 + <FormGroup controlId="formControlsSelect">
  112 + <Label>Gender</Label>
  113 + <FormControl componentClass="select"
  114 + placeholder="select"
  115 + value={props.getValue('gender')}
  116 + onChange={e => props.setValue('gender', e.target.value)}
  117 + >
  118 + <option value="male">Male</option>
  119 + <option value="female">Female</option>
  120 + </FormControl>
  121 + </FormGroup>
  122 + </Col>
  123 + </Row>
  124 + </fieldset>
  125 + )}
  126 + {props.currentStep === 1 && (
  127 + <fieldset title="2">
  128 + <legend className="text-semibold">Address</legend>
  129 + <Row>
  130 + <Col xs={12} sm={6}>
  131 + <FormGroup controlId="rollNo">
  132 + <Label>Roll No</Label>
  133 + <FormControl
  134 + type="text"
  135 + value={props.getValue('rollNo')}
  136 + placeholder="Roll No"
  137 + onChange={e => props.setValue('rollNo', e.target.value)}
  138 + />
  139 + </FormGroup>
  140 + </Col>
  141 + <Col xs={12} sm={6}>
  142 + <FormGroup controlId="class">
  143 + <Label>Class</Label>
  144 + <FormControl
  145 + type="text"
  146 + value={props.getValue('class')}
  147 + placeholder="7"
  148 + onChange={e => props.setValue('class', e.target.value)}
  149 + />
  150 + </FormGroup>
  151 + </Col>
  152 + </Row>
  153 + <Row>
  154 + <Col xs={12} sm={6}>
  155 + <FormGroup controlId="section">
  156 + <Label>Section</Label>
  157 + <FormControl
  158 + type="text"
  159 + value={props.getValue('section')}
  160 + placeholder="B"
  161 + onChange={e => props.setValue('section', e.target.value)}
  162 + />
  163 + </FormGroup>
  164 + </Col>
  165 + <Col xs={12} sm={6}>
  166 + <FormGroup controlId="community">
  167 + <Label>Community</Label>
  168 + <FormControl
  169 + type="text"
  170 + value={props.getValue('community')}
  171 + placeholder="General"
  172 + onChange={e => props.setValue('community', e.target.value)}
  173 + />
  174 + </FormGroup>
  175 + </Col>
  176 + </Row>
  177 + <Row>
  178 + <Col xs={12} sm={6}>
  179 + <FormGroup controlId="bloodGroup">
  180 + <Label>bloodGroup</Label>
  181 + <FormControl
  182 + type="text"
  183 + value={props.getValue('bloodGroup')}
  184 + placeholder="B+"
  185 + onChange={e => props.setValue('bloodGroup', e.target.value)}
  186 + />
  187 + </FormGroup>
  188 + </Col>
  189 + <Col xs={12} sm={6}>
  190 + <FormGroup controlId="phone">
  191 + <Label>Phone</Label>
  192 + <FormControl
  193 + type="text"
  194 + value={props.getValue('phone')}
  195 + placeholder="9999999999"
  196 + onChange={e => props.setValue('phone', e.target.value)}
  197 + />
  198 + </FormGroup>
  199 + </Col>
  200 + </Row>
  201 + <Row>
  202 + <Col xs={12} sm={6}>
  203 + <FormGroup controlId="address">
  204 + <Label>Address</Label>
  205 + <FormControl
  206 + type="text"
  207 + value={props.getValue('address')}
  208 + placeholder="#876, Street, town"
  209 + onChange={e => props.setValue('address', e.target.value)}
  210 + />
  211 + </FormGroup>
  212 + </Col>
  213 + <Col xs={12} sm={6}>
  214 + <FormGroup controlId="city">
  215 + <Label>City</Label>
  216 + <FormControl
  217 + type="text"
  218 + value={props.getValue('city')}
  219 + placeholder="Chennai"
  220 + onChange={e => props.setValue('city', e.target.value)}
  221 + />
  222 + </FormGroup>
  223 + </Col>
  224 + </Row>
  225 + <Row>
  226 + <Col xs={12} sm={6}>
  227 + <FormGroup controlId="state">
  228 + <Label>State</Label>
  229 + <FormControl
  230 + type="text"
  231 + value={props.getValue('state')}
  232 + placeholder="Tamilnadu"
  233 + onChange={e => props.setValue('state', e.target.value)}
  234 + />
  235 + </FormGroup>
  236 + </Col>
  237 + </Row>
  238 + </fieldset>
  239 + )}
  240 + <div style={{ textAlign: 'left' }}>
  241 + {props.currentStep > 0 && (
  242 + <div style={{ display: 'inline-block', marginRight: 10 }}>
  243 + <Button onClick={props.onBackClick}>
  244 + <i className="icon-arrow-left13 position-left"></i>
  245 + BACK
  246 + </Button>
  247 +
  248 + </div>
  249 + )}
  250 + <div style={{ display: 'inline-block' }}>
  251 + <Button
  252 + bsStyle="primary"
  253 + onClick={props.onNextClick}
  254 + >
  255 + NEXT
  256 + <i className="icon-arrow-right14 position-right" />
  257 + </Button>
  258 + </div>
  259 + </div>
  260 + </div>
  261 +)
  262 +
  263 +StudentForm.propTypes = {
  264 + currentStep: PropTypes.number.isRequired,
  265 + onNextClick: PropTypes.func.isRequired,
  266 + onBackClick: PropTypes.func.isRequired,
  267 + setValue: PropTypes.func.isRequired,
  268 + getValue: PropTypes.func.isRequired,
  269 +}
  270 +
  271 +export default StudentForm
... ...
imports/client/views/org/admin/students/addStudent.js
... ... @@ -6,7 +6,7 @@ import { Link,browserHistory } from &#39;react-router&#39;;
6 6 import { FormGroup,Panel,Table,
7 7 ButtonToolbar,Modal,
8 8 FormControl,Glyphicon,Button } from 'react-bootstrap';
9   -import { AddStudentForm } from './addStudentForm';
  9 +import { AddStudentFormContainer } from './AddStudentFormContainer';
10 10  
11 11 const style = {
12 12 margin: 12,
... ... @@ -49,7 +49,7 @@ export class AddStudent extends Component {
49 49 <Modal.Title id="contained-modal-title-lg">New Student</Modal.Title>
50 50 </Modal.Header>
51 51 <Modal.Body>
52   - <AddStudentForm />
  52 + <AddStudentFormContainer />
53 53 </Modal.Body>
54 54 <Modal.Footer>
55 55 <Button onClick={this.hideModal}>Close</Button>
... ...
... ... @@ -70,6 +70,7 @@
70 70 "fs": "0.0.1-security",
71 71 "jquery": "^2.2.4",
72 72 "jquery-validation": "^1.15.1",
  73 + "lodash": "^4.17.4",
73 74 "material-fabmenu": "0.0.1",
74 75 "material-ui": "^0.17.1",
75 76 "meteor-node-stubs": "^0.2.6",
... ...