Skip to content
This repository was archived by the owner on May 24, 2019. It is now read-only.

Commit 0333038

Browse files
authored
Merge pull request #1 from nucleartide/changeset
Add beginnings of example
2 parents 57aaf38 + 8e220ca commit 0333038

16 files changed

+1482
-240
lines changed

example/components/Validate.tsx

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as React from 'react';
2+
import { Component } from 'react';
3+
import { IValidationMap } from 'validated-proxy/dist/types/utils/validator-lookup';
4+
import { validatedProxy, BufferedProxy } from 'validated-proxy';
5+
6+
interface HelperProps {
7+
model: BufferedProxy
8+
set: <T>(name: string, value: T) => void
9+
reset: () => void
10+
isPristine: boolean
11+
hasErrors: boolean
12+
flush: () => void
13+
}
14+
15+
type RenderProp = (p: HelperProps) => JSX.Element
16+
17+
interface Props<T> {
18+
model: T
19+
as: IValidationMap
20+
children: RenderProp
21+
}
22+
23+
interface State {
24+
model: BufferedProxy
25+
}
26+
27+
class Validate<Model extends {}> extends Component<Props<Model>, State> {
28+
constructor(p: Props<Model>) {
29+
super(p);
30+
this.state = {
31+
model: validatedProxy(p.model, { validations: p.as }),
32+
};
33+
}
34+
35+
set = <V extends {}>(name: string, value: V) => {
36+
this.state.model[name] = value;
37+
this.setState({ model: this.state.model });
38+
}
39+
40+
reset = () => {
41+
this.state.model.reset();
42+
this.setState({ model: this.state.model });
43+
}
44+
45+
flush = () => {
46+
this.state.model.flush();
47+
this.setState({ model: this.state.model });
48+
}
49+
50+
render() {
51+
return this.props.children({
52+
model: this.state.model,
53+
set: this.set,
54+
reset: this.reset,
55+
isPristine: Object.keys(this.state.model.cache).length === 0,
56+
hasErrors: this.state.model.errors.length > 0,
57+
flush: this.flush,
58+
});
59+
}
60+
}
61+
62+
export default Validate;

example/components/ValidatedField.tsx

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import * as React from 'react';
2+
import { BufferedProxy } from 'validated-proxy';
3+
4+
interface Props {
5+
model: BufferedProxy
6+
property: string
7+
setProperty: (name: string, value: string) => void
8+
type: 'text' | 'number'
9+
}
10+
11+
interface ComputedProps extends Props {
12+
messages: Array<string>
13+
}
14+
15+
const transform = (p: Props): ComputedProps => ({
16+
messages: p.model.cache
17+
&& p.model.cache[p.property]
18+
&& p.model.cache[p.property].validations
19+
.filter(v => !v.validation)
20+
.map(v => v.message)
21+
|| [],
22+
...p,
23+
});
24+
25+
const component = (p: ComputedProps) => (
26+
<>
27+
<label htmlFor={p.property}>{p.property}</label>
28+
<input
29+
type={p.type}
30+
id={p.property}
31+
value={p.model[p.property]}
32+
onChange={e => p.setProperty(p.property, e.target.value)}
33+
/>
34+
35+
{p.messages.length > 0 &&
36+
<ul>
37+
{p.messages.map(m => <li key={m}>{m}</li>)}
38+
</ul>
39+
}
40+
</>
41+
);
42+
43+
// def validated_field(p) do
44+
// p
45+
// |> transform
46+
// |> component
47+
// end
48+
const ValidatedField = (p: Props) => (
49+
component(
50+
transform(
51+
p
52+
)
53+
)
54+
);
55+
56+
export default ValidatedField;

example/index.css

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
form {
2+
background-color: papayawhip;
3+
}

example/index.tsx

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import * as React from 'react';
2+
import { render } from 'react-dom';
3+
4+
import Adult from './validations/adult';
5+
import Child from './validations/child';
6+
import Validate from './components/Validate';
7+
import ValidatedField from './components/ValidatedField';
8+
9+
window.user = {
10+
firstName: 'Billy',
11+
lastName: 'Bob',
12+
email: '',
13+
job: '',
14+
age: 10,
15+
};
16+
17+
// Try swapping `as={Adult}` with `as={Child}`.
18+
render(
19+
<Validate model={window.user} as={Adult}>
20+
{({ model, set, reset, isPristine, hasErrors, flush }) => (
21+
<form onSubmit={e => {
22+
e.preventDefault();
23+
flush();
24+
}}>
25+
<ValidatedField
26+
type="text"
27+
model={model}
28+
property="firstName"
29+
setProperty={set}
30+
/>
31+
32+
<ValidatedField
33+
type="text"
34+
model={model}
35+
property="lastName"
36+
setProperty={set}
37+
/>
38+
39+
<ValidatedField
40+
type="text"
41+
model={model}
42+
property="email"
43+
setProperty={set}
44+
/>
45+
46+
<ValidatedField
47+
type="text"
48+
model={model}
49+
property="job"
50+
setProperty={set}
51+
/>
52+
53+
<ValidatedField
54+
type="number"
55+
model={model}
56+
property="age"
57+
setProperty={(name, value) => set(name, parseInt(value, 10))}
58+
/>
59+
60+
<button type="submit" disabled={isPristine || hasErrors}>Save</button>
61+
<button type="button" onClick={reset}>Reset</button>
62+
</form>
63+
)}
64+
</Validate>,
65+
document.getElementById('app')
66+
);

example/validations/adult.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import User from './user';
2+
import isNumber from '../validators/number';
3+
4+
export default {
5+
...User,
6+
age: isNumber({ op: '>=', value: 18 }),
7+
};

example/validations/child.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import User from './user';
2+
import isNumber from '../validators/number';
3+
4+
export default {
5+
...User,
6+
age: isNumber({ op: '<', value: 18 }),
7+
};

example/validations/user.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import isPresent from '../validators/presence';
2+
import hasLength from '../validators/length';
3+
import isUnique from '../validators/uniqueness';
4+
import isEmail from '../validators/email';
5+
6+
export default {
7+
firstName: [
8+
isPresent(),
9+
hasLength({ min: 2 }),
10+
],
11+
lastName: [
12+
isPresent(),
13+
hasLength({ min: 2 }),
14+
],
15+
email: [
16+
isUnique(),
17+
isEmail(),
18+
],
19+
job: [
20+
isPresent(),
21+
],
22+
};

example/validators/email.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const Email = /^(([^<>()\[\]\\.,;:\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,}))$/;
2+
3+
// Validate that an email has the correct format.
4+
const validateEmail =
5+
() =>
6+
(key: string, newValue: string, oldValue: string) => ({
7+
message: 'Invalid email.',
8+
validation: Email.test(newValue),
9+
});
10+
11+
export default validateEmail;

example/validators/length.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
interface Options {
2+
min: number
3+
}
4+
5+
// Validate that a string has minimum length.
6+
const validateLength =
7+
({ min }: Options) =>
8+
(key: string, newValue: string, oldValue: string) => ({
9+
message: `${key} is too short.`,
10+
validation: newValue.length >= min,
11+
});
12+
13+
export default validateLength;

example/validators/number.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { IValidationMeta } from 'validated-proxy/dist/types/validation-result';
2+
3+
interface LessThan {
4+
op: '<',
5+
value: number
6+
}
7+
8+
interface GreaterThanOrEqual {
9+
op: '>=',
10+
value: number
11+
}
12+
13+
type Options =
14+
| LessThan
15+
| GreaterThanOrEqual
16+
;
17+
18+
const welp = (x: never): never => {
19+
throw new Error('Unexpected object.');
20+
};
21+
22+
const message = (o: Options, key: string, newValue: number): string => {
23+
switch (o.op) {
24+
case '<':
25+
return `${key} is not less than ${o.value}.`;
26+
case '>=':
27+
return `${key} is not greater than or equal to ${o.value}.`;
28+
default:
29+
return welp(o);
30+
}
31+
};
32+
33+
const validation = (o: Options, key: string, newValue: number): boolean => {
34+
switch (o.op) {
35+
case '<':
36+
return newValue < o.value;
37+
case '>=':
38+
return newValue >= o.value;
39+
default:
40+
return welp(o);
41+
}
42+
};
43+
44+
// Validate that a number satisfies comparison rules.
45+
const validateNumber =
46+
(o: Options) =>
47+
(key: string, newValue: number, oldValue: number): IValidationMeta => ({
48+
message: message(o, key, newValue),
49+
validation: validation(o, key, newValue),
50+
});
51+
52+
export default validateNumber;

example/validators/presence.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Validate that a value is truthy.
2+
const validatePresence =
3+
() =>
4+
<T extends {}>(key: string, newValue: T, oldValue: T) => ({
5+
message: `${key} is missing.`,
6+
validation: Boolean(newValue),
7+
});
8+
9+
export default validatePresence;

example/validators/uniqueness.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
const ReservedEmails = ['foo@bar.com', 'admin@example.com'];
2+
3+
// Validate that an email is not reserved.
4+
const validateUniqueness =
5+
() =>
6+
(key: string, newValue: string, oldValue: string) => ({
7+
message: `Email is taken.`,
8+
validation: ReservedEmails.indexOf(newValue) === -1,
9+
});
10+
11+
export default validateUniqueness;

index.html

+3-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
<head>
44
<meta charset="UTF-8">
55
<title></title>
6+
<link rel="stylesheet" href="./example/index.css">
67
</head>
78
<body>
8-
<script src="./index.ts"></script>
9+
<div id="app"></div>
10+
<script src="./example/index.tsx"></script>
911
</body>
1012
</html>

index.tsx

-17
This file was deleted.

0 commit comments

Comments
 (0)