Use formsy-react for validation

Complete validation for username/password form (except for checking if a username exists).
This commit is contained in:
Ray Schamp 2016-05-12 17:42:04 -04:00
parent de3151924c
commit b5c615b1fa
12 changed files with 451 additions and 245 deletions

View file

@ -43,6 +43,8 @@
"exenv": "1.2.0",
"fastly": "1.2.1",
"file-loader": "0.8.4",
"formsy-react": "0.18.0",
"formsy-react-components": "0.7.1",
"git-bundle-sha": "0.0.2",
"glob": "5.0.15",
"json-loader": "0.5.2",

View file

@ -0,0 +1,18 @@
var classNames = require('classnames');
var FRCCheckboxGroup = require('formsy-react-components').CheckboxGroup;
var React = require('react');
var CheckboxGroup = React.createClass({
type: 'CheckboxGroup',
render: function () {
var classes = classNames(
'checkbox-group',
this.props.className
);
return (
<FRCCheckboxGroup {... this.props} className={classes} />
);
}
});
module.exports = CheckboxGroup;

View file

@ -0,0 +1,18 @@
var classNames = require('classnames');
var FRCCheckbox = require('formsy-react-components').Checkbox;
var React = require('react');
var Checkbox = React.createClass({
type: 'Checkbox',
render: function () {
var classes = classNames(
'checkbox',
this.props.className
);
return (
<FRCCheckbox {... this.props} className={classes} />
);
}
});
module.exports = Checkbox;

View file

@ -0,0 +1,24 @@
var classNames = require('classnames');
var Formsy = require('formsy-react');
var React = require('react');
var validations = require('./validations');
for (var validation in validations) {
Formsy.addValidationRule(validation, validations[validation]);
}
var Form = React.createClass({
render: function () {
var classes = classNames(
'form',
this.props.className
);
return (
<Formsy.Form {... this.props} className={classes}>
{this.props.children}
</Formsy.Form>
);
}
});
module.exports = Form;

View file

@ -19,10 +19,6 @@ module.exports = {
navigation: null
};
},
onSubmit: function (e) {
e.preventDefault();
this.props.onNextStep();
},
render: function () {
var classes = classNames(
'step',
@ -33,13 +29,7 @@ module.exports = {
<img className="icon" src={this.props.icon} />
{this.props.description}
{this.props.navigation}
{React.Children.map(this.props.children, function (child){
if (child.type === 'form') {
return React.cloneElement(child, {onSubmit: this.onSubmit});
} else {
return child;
}
}, this)}
{this.props.children}
</div>
);
}
@ -85,9 +75,10 @@ module.exports = {
<div {... this.props} className={classes}>
{React.Children.map(this.props.children, function (child, id) {
if (id === this.props.step) {
return React.cloneElement(child, {onNextStep: function () {
this.props.onSetStep(this.props.step + 1);
}.bind(this)});
var props = {
navigation: navigation
};
return React.cloneElement(child, props);
}
}, this)}
</div>

View file

@ -1,22 +1,25 @@
var React = require('react');
var classNames = require('classnames');
var FRCInput = require('formsy-react-components').Input;
var React = require('react');
var validateMixin = require('./validateMixin.jsx');
require('./input.scss');
var Input = React.createClass({
type: 'Input',
propTypes: {
getDefaultProps: function () {
return {};
},
render: function () {
var classes = classNames(
'input',
this.props.className
);
return (
<input {... this.props} className={classes} />
return (this.props.type === 'submit' || this.props.noformsy ?
<input {... this.props} className={classes} /> :
<FRCInput {... this.props} className={classes} />
);
}
});
module.exports = Input;
module.exports = validateMixin(Input);

View file

@ -0,0 +1,18 @@
var classNames = require('classnames');
var FRCRadioGroup = require('formsy-react-components').RadioGroup;
var React = require('react');
var RadioGroup = React.createClass({
type: 'RadioGroup',
render: function () {
var classes = classNames(
'radio-group',
this.props.className
);
return (
<FRCRadioGroup {... this.props} className={classes} />
);
}
});
module.exports = RadioGroup;

View file

@ -1,5 +1,6 @@
var React = require('react');
var classNames = require('classnames');
var FRCSelect = require('formsy-react-components').Select;
var React = require('react');
require('./select.scss');
@ -14,9 +15,7 @@ var Select = React.createClass({
this.props.className
);
return (
<select {... this.props} className={classes}>
{this.props.children}
</select>
<FRCSelect {... this.props} className={classes} />
);
}
});

View file

@ -1,25 +1,18 @@
var React = require('react');
var classNames = require('classnames');
var FRCTextarea = require('formsy-react-components').Textarea;
var React = require('react');
require('./textarea.scss');
var TextArea = React.createClass({
type: 'TextArea',
getDefaultProps: function () {
return {
rows: 5,
cols: 40
}
},
render: function () {
var classes = classNames(
'textarea',
this.props.className
);
return (
<textarea {... this.props} className={classes}>
{this.props.children}
</textarea>
<FRCTextarea {... this.props} className={classes} />
);
}
});

View file

@ -0,0 +1,24 @@
var defaults = require('lodash.defaultsdeep');
var React = require('react');
var validateMixin = function (Component) {
var ValidatedComponent = React.createClass({
getDefaultValidationErrors: function () {
return {
isDefaultRequiredValue: 'This field is required'
};
},
render: function () {
var validationErrors = defaults(
this.getDefaultValidationErrors(),
this.props.validationErrors
);
return (
<Component {...this.props} validationErrors={validationErrors} />
);
}
});
return ValidatedComponent;
};
module.exports = validateMixin;

View file

@ -0,0 +1,10 @@
var Validations = {
notEquals: function (values, value, neq) {
return value !== neq;
},
notEqualsField: function (values, value, field) {
return value !== values[field];
}
};
module.exports = Validations;

View file

@ -1,35 +1,337 @@
var classNames = require('classnames');
var defaults = require('lodash.defaultsdeep');
var React = require('react');
var render = require('../../lib/render.jsx');
var Button = require('../../components/forms/button.jsx');
var Checkbox = require('../../components/forms/checkbox.jsx');
var CheckboxGroup = require('../../components/forms/checkbox-group.jsx');
var Form = require('../../components/forms/form.jsx');
var formset = require('../../components/forms/formset.jsx');
var FormSet = formset.FormSet;
var FormStep = formset.FormStep;
var Input = require('../../components/forms/input.jsx');
var Label = require('../../components/forms/label.jsx');
var Page = require('../../components/page/www/page.jsx');
var RadioGroup = require('../../components/forms/radio-group.jsx');
var Select = require('../../components/forms/select.jsx');
var TextArea = require('../../components/forms/textarea.jsx');
var COUNTRIES = require('./countries.json');
require('./teacherregistration.scss');
var UsernameStep = React.createClass({
render: function () {
return (
<FormStep title="Create a Teacher Account"
description={
<p>
Creating a Teacher Account requires additional information
for review.
<strong>The approval process can take up to 24 hours</strong>
</p>}>
<Form onValidSubmit={this.props.onNextStep} noValidate>
<Input label="Username"
type="text"
name="username"
validations={{
matchRegexp: /^[\w-]*$/,
minLength: 3,
maxLength: 20
}}
validationErrors={{
matchRegexp: 'Your username may only contain characters and -',
minLength: 'Usernames must be at least three characters',
maxLength: 'Usernames must be at most 20 characters'
}}
required />
<Input label="Password"
type="password"
name="password"
validations={{
minLength: 6,
notEquals: 'password',
notEqualsField: 'username'
}}
validationErrors={{
minLength: 'Passwords must be at least six characters',
notEquals: 'Your password may not be "password"',
notEqualsField: 'Your password may not be your username'
}}
required />
<Input label="Confirm Password"
type="password"
name="passwordConfirmation"
validations="equalsField:password"
validationError="The passwords do not match"
required/>
<Button type="submit">Next Step</Button>
</Form>
</FormStep>
);
}
});
var DemographicsStep = React.createClass({
onSubmit: function () {
this.props.onNextStep();
},
render: function () {
var countryOptions = Object.keys(COUNTRIES).map(function (code) {
return {value: code, label: COUNTRIES[code]};
});
var monthOptions = [
'January', 'February', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December'
].map(function (label, id) {
return {value: id+1, label: label};
});
var yearOptions = Array.apply(null, Array(100)).map(function (v, id) {
var year = 2016 - id;
return {value: year, label: year};
});
return (
<FormStep title="Demographics"
description={
<p>
Your responses to these questions will be kept private.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}>
<Form onSubmit={this.onSubmit}>
<Select label="Birth Month" name="month" options={monthOptions} />
<Select label="Birth Yeah" name="year" options={yearOptions} />
<RadioGroup label="Gender"
name="gender"
options={[
{value: 'female', label: 'Female'},
{value: 'male', label: 'Male'},
{value: 'other', label: 'Other'}
]}
/>
<Input name="genderOther" type="text" />
<Select label="Country" name="country" options={countryOptions} />
<Button type="submit">Next Step</Button>
</Form>
</FormStep>
);
}
});
var NameStep = React.createClass({
onSubmit: function () {
this.props.onNextStep();
},
render: function () {
return (
<FormStep title="First &amp; Last Name"
description={
<p>
Your responses to these questions will be kept private.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}>
<Form onSubmit={this.onSubmit}>
<Label htmlFor="first">First Name</Label>
<Input type="text" name="first" />
<Label htmlFor="last">Last Name</Label>
<Input type="text" name="last" />
<Button type="submit">Next Step</Button>
</Form>
</FormStep>
);
}
});
var PhoneNumberStep = React.createClass({
onSubmit: function () {
this.props.onNextStep();
},
render: function () {
return (
<FormStep title="Phone Number"
description={
<p>
Your responses to these questions will be kept private.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}>
<Form onSubmit={this.onSubmit}>
<Label htmlFor="phone">Phone Number</Label>
<Input type="tel" name="phone" />
<Checkbox name="phoneConsent" />
<Label htmlFor="phoneConsent">
Yes, I consent to lorem ipsum dolor sit amet,
consectetur adipiscing elit.
</Label>
<Button type="submit">Next Step</Button>
</Form>
</FormStep>
);
}
});
var OrganizationStep = React.createClass({
onSubmit: function () {
this.props.onNextStep();
},
render: function () {
var organizationOptions = [
'Elementary School', 'Middle School', 'High School', 'University / College',
'Museum', 'Library', 'Camp'
].map(function (type) { return {value: type, label: type}; });
return (
<FormStep title="Organization"
description={
<p>
Your responses to these questions will be kept private.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}>
<Form onSubmit={this.onSubmit}>
<Label htmlFor="organization">Organization</Label>
<Input type="text" name="organization" />
<Label htmlFor="title">Title / Position</Label>
<Input type="text" name="title" />
<CheckboxGroup label="Type of Organization"
options={organizationOptions} />
<Checkbox name="organizationType" value="other" />
<Input type="text" name="organizationTypeOther" />
<Label htmlFor="website">Website URL (not required)</Label>
<Input type="url" name="website" />
<Button type="submit">Next Step</Button>
</Form>
</FormStep>
);
}
});
var AddressStep = React.createClass({
onSubmit: function () {
this.props.onNextStep();
},
render: function () {
var countryOptions = Object.keys(COUNTRIES).map(function (code) {
return {value: code, label: COUNTRIES[code]};
});
var stateOptions = ['every','state','in','the','world'].map(function (name) {
return {value: name, label: name};
});
return (
<FormStep title="Address"
description={
<p>
Your responses to these questions will be kept private.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}>
<Form onSubmit={this.onSubmit}>
<Label htmlFor="addressCountry">Country</Label>
<Select name="addressCountry" options={countryOptions} />
<Label htmlFor="addressLine1">Address Line 1</Label>
<Input type="text" name="addressLine1" />
<Label htmlFor="addressLine2">Address Line 2</Label>
<Input type="text" name="addressLine2" />
<Label htmlFor="addressCity">City</Label>
<Input type="text" name="addressCity" />
<Label htmlFor="addressZip">Zip Code</Label>
<Input type="text" name="addressZip" />
<Label htmlFor="addressState">State / Province</Label>
<Select name="addressState" options={stateOptions} />
<Button type="submit">Next Step</Button>
</Form>
</FormStep>
);
}
});
var UseScratchStep = React.createClass({
onSubmit: function () {
this.props.onNextStep();
},
render: function () {
return (
<FormStep title="How do you use Scratch?"
description={
<p>
Tell us a little how you plan to use Scratch.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}>
<Form onSubmit={this.onSubmit}>
<Label htmlFor="useScratch">How do you use Scratch?</Label>
<TextArea name="useScratch" />
<Button type="submit">Next Step</Button>
</Form>
</FormStep>
);
}
});
var EmailStep = React.createClass({
onSubmit: function () {
this.props.onNextStep();
},
render: function () {
return (
<FormStep title="Email Address"
description={
<p>
We will send you a <strong>confirmation email</strong> that will
allow you to access your Scratch Teacher Account.
</p>}>
<Form onSubmit={this.onSubmit}>
<Label htmlFor="email">Email</Label>
<Input type="text" name="email" />
<Label htmlFor="confirmEmail">Confirm Email</Label>
<Input type="text" name="confirmEmail" />
<Button type="submit">Next Step</Button>
</Form>
</FormStep>
);
}
});
var LastStep = React.createClass({
render: function () {
return (
<FormStep title="Almost Done"
description={
<p>
Lorem ipsum dolor sit amet
</p>}>
<div className="confirm">
<h2>Confirm Your Email</h2>
<p>
Click the link in the confirmation email that we
sent to the following address:<br />
<strong>{this.state.email}</strong>
</p>
<div className="box-footer">
<a onClick="">Wrong email?</a>
<a onClick="">Having trouble?</a>
</div>
</div>
<div className="wait">
<h2>Wait for Approval</h2>
<p>
Your information is being reviewed. Please be
patient, the approval process can take up to 24hrs.
</p>
</div>
</FormStep>
);
}
});
var TeacherRegistration = React.createClass({
type: 'TeacherRegistration',
getInitialState: function () {
return {
step: 0
step: 0,
formData: {}
};
},
setStep: function (step) {
this.setState({step: step});
},
advanceStep: function (formData) {
formData = formData || {};
this.setState({
step: this.state.step + 1,
formData: defaults({}, formData, this.state.formData)
});
},
render: function () {
var months = [
'January', 'February', 'March', 'April', 'May', 'June', 'July',
'August', 'September', 'October', 'November', 'December'];
var countries = require('./countries.json');
var classes = classNames(
'teacher-registration',
'inner',
@ -37,212 +339,16 @@ var TeacherRegistration = React.createClass({
return (
<div {...this.props} className={classes}>
<FormSet {... this.props}
step={this.state.step}
onSetStep={this.setStep}>
<FormStep title="Create a Teacher Account"
description={
<p>
Creating a Teacher Account requires additional information
for review.
<strong>The approval process can take up to 24 hours</strong>
</p>}
key="step1">
<form>
<Label htmlFor="username">Username</Label>
<Input type="text" name="username" />
<Label htmlFor="password">Password</Label>
<Input type="password" name="password" />
<Label htmlFor="passwordConfirmation">Confirm Password</Label>
<Input type="password" name="passwordConfirmation" />
<Button type="submit">Next Step</Button>
</form>
</FormStep>
<FormStep title="Demographics"
description={
<p>
Your responses to these questions will be kept private.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}
key="step2">
<form>
<Label htmlFor="month">Birth Month</Label>
<Select name="month">
{months.map(function (name, id) {
return (<option value={id+1} key={id}>{name}</option>);
})}
</Select>
<Label htmlFor="year">Birth Yeah</Label>
<Select name="year">
{Array.apply(null, Array(100)).map(function (v, id) {
return (<option value={2016-id} key={id}>{2016-id}</option>);
})}
</Select>
<Label>Gender</Label>
<Input type="radio" name="gender" id="genderFemale" value="female" />
<Label htmlFor="genderFemale">Female</Label>
<Input type="radio" name="gender" id="genderMale" value="male" />
<Label htmlFor="genderMale">Male</Label>
<Input type="radio" name="gender" value="genderOther" />
<Input name="genderOther" type="text" />
<Label htmlFor="country">Country</Label>
<Select name="country">
{Object.keys(countries).map(function (code, id) {
return (<option value={code} key={id}>{countries[code]}</option>);
})}
</Select>
<Button type="submit">Next Step</Button>
</form>
</FormStep>
<FormStep title="First &amp; Last Name"
description={
<p>
Your responses to these questions will be kept private.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}
key="step3">
<form>
<Label htmlFor="first">First Name</Label>
<Input type="text" name="first" />
<Label htmlFor="last">Last Name</Label>
<Input type="text" name="last" />
<Button type="submit">Next Step</Button>
</form>
</FormStep>
<FormStep title="Phone Number"
description={
<p>
Your responses to these questions will be kept private.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}
key="step4">
<form>
<Label htmlFor="phone">Phone Number</Label>
<Input type="tel" name="phone" />
<Input type="checkbox" name="phoneConsent" />
<Label htmlFor="phoneConsent">
Yes, I consent to lorem ipsum dolor sit amet,
consectetur adipiscing elit.
</Label>
<Button type="submit">Next Step</Button>
</form>
</FormStep>
<FormStep title="Organization"
description={
<p>
Your responses to these questions will be kept private.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}
key="step5">
<form>
<Label htmlFor="organization">Organization</Label>
<Input type="text" name="organization" />
<Label htmlFor="title">Title / Position</Label>
<Input type="text" name="title" />
<Label>Type of Organization</Label>
{['Elementary School', 'Middle School',
'High School', 'University / College',
'Museum', 'Library', 'Camp'].map(function (type, id) {
var typeId = 'organizationType' + id;
return [
<Input type="checkbox"
name="organizationType"
id={typeId}
value={type} />,
<Label htmlFor={typeId}>{type}</Label>
];
})}
<Input type="checkbox" name="organizationType" value="other" />
<Input type="text" name="organizationTypeOther" />
<Label htmlFor="website">Website URL (not required)</Label>
<Input type="url" name="website" />
<Button type="submit">Next Step</Button>
</form>
</FormStep>
<FormStep title="Address"
description={
<p>
Your responses to these questions will be kept private.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}
key="step6">
<form>
<Label htmlFor="addressCountry">Country</Label>
<Select name="addressCountry">
{Object.keys(countries).map(function (code, id) {
return (<option value={code} key={id}>{countries[code]}</option>);
})}
</Select>
<Label htmlFor="addressLine1">Address Line 1</Label>
<Input type="text" name="addressLine1" />
<Label htmlFor="addressLine2">Address Line 2</Label>
<Input type="text" name="addressLine2" />
<Label htmlFor="addressCity">City</Label>
<Input type="text" name="addressCity" />
<Label htmlFor="addressZip">Zip Code</Label>
<Input type="text" name="addressZip" />
<Label htmlFor="addressState">State / Province</Label>
<Select name="addressState">
{['every','state','in','the','world'].map(function (name, id) {
return <option value={name} key={id}>{name}</option>;
})}
</Select>
<Button type="submit">Next Step</Button>
</form>
</FormStep>
<FormStep title="How do you use Scratch?"
description={
<p>
Tell us a little how you plan to use Scratch.
Why do we ask for this information <a onClick={this.handle}>?</a>
</p>}
key="step7">
<form>
<Label htmlFor="useScratch">How do you use Scratch?</Label>
<TextArea name="useScratch" />
<Button type="submit">Next Step</Button>
</form>
</FormStep>
<FormStep title="Email Address"
description={
<p>
We will send you a <strong>confirmation email</strong> that will
allow you to access your Scratch Teacher Account.
</p>}
key="step8">
<form>
<Label htmlFor="email">Email</Label>
<Input type="text" name="email" />
<Label htmlFor="confirmEmail">Confirm Email</Label>
<Input type="text" name="confirmEmail" />
<Button type="submit">Next Step</Button>
</form>
</FormStep>
<FormStep title="Almost Done"
description={
<p>
Lorem ipsum dolor sit amet
</p>}
key="step8">
<div className="confirm">
<h2>Confirm Your Email</h2>
<p>
Click the link in the confirmation email that we
sent to the following address:<br />
<strong>{this.state.email}</strong>
</p>
<div className="box-footer">
<a onClick="">Wrong email?</a>
<a onClick="">Having trouble?</a>
</div>
</div>
<div className="wait">
<h2>Wait for Approval</h2>
<p>
Your information is being reviewed. Please be
patient, the approval process can take up to 24hrs.
</p>
</div>
</FormStep>
step={this.state.step}>
<UsernameStep onNextStep={this.advanceStep} />
<DemographicsStep onNextStep={this.advanceStep} />
<NameStep onNextStep={this.advanceStep} />
<PhoneNumberStep onNextStep={this.advanceStep} />
<OrganizationStep onNextStep={this.advanceStep} />
<AddressStep onNextStep={this.advanceStep} />
<UseScratchStep onNextStep={this.advanceStep} />
<EmailStep onNextStep={this.advanceStep} />
<LastStep />
</FormSet>
</div>
);