├── ContactForm.js
├── ContactItem.js
├── ContactsView.js
├── README.md
├── index.html
├── main.js
├── polyfills.js
└── style.css
/ContactForm.js:
--------------------------------------------------------------------------------
1 | var ContactForm = React.createClass({
2 | propTypes: {
3 | value: React.PropTypes.object.isRequired,
4 | onChange: React.PropTypes.func.isRequired,
5 | onSubmit: React.PropTypes.func.isRequired,
6 | },
7 |
8 | onNameInput: function(e) {
9 | this.props.onChange(Object.assign({}, this.props.value, {name: e.target.value}))
10 | },
11 |
12 | onEmailInput: function(e) {
13 | this.props.onChange(Object.assign({}, this.props.value, {email: e.target.value}))
14 | },
15 |
16 | onDescriptionInput: function(e) {
17 | this.props.onChange(Object.assign({}, this.props.value, {description: e.target.value}))
18 | },
19 |
20 | onSubmit: function(e) {
21 | e.preventDefault()
22 | this.props.onSubmit()
23 | },
24 |
25 | render: function() {
26 | var errors = this.props.value.errors || {}
27 |
28 | return (
29 | React.createElement('form', {onSubmit: this.onSubmit, className: 'ContactForm', noValidate: true},
30 | React.createElement('input', {
31 | type: 'text',
32 | className: errors.name && 'ContactForm-error',
33 | placeholder: 'Name (required)',
34 | onInput: this.onNameInput,
35 | value: this.props.value.name,
36 | ref: 'name',
37 | }),
38 | React.createElement('input', {
39 | type: 'email',
40 | className: errors.email && 'ContactForm-error',
41 | placeholder: 'Email (required)',
42 | onInput: this.onEmailInput,
43 | value: this.props.value.email,
44 | noValidate: true,
45 | }),
46 | React.createElement('textarea', {
47 | placeholder: 'Description',
48 | onInput: this.onDescriptionInput,
49 | value: this.props.value.description,
50 | }),
51 | React.createElement('button', {type: 'submit'}, "Add Contact")
52 | )
53 | )
54 | },
55 | });
56 |
--------------------------------------------------------------------------------
/ContactItem.js:
--------------------------------------------------------------------------------
1 | var ContactItem = React.createClass({
2 | propTypes: {
3 | name: React.PropTypes.string.isRequired,
4 | email: React.PropTypes.string.isRequired,
5 | description: React.PropTypes.string,
6 | },
7 |
8 | render: function() {
9 | return (
10 | React.createElement('div', {className: 'ContactItem'},
11 | React.createElement('div', {className: 'ContactItem-name'}, this.props.name),
12 | React.createElement('div', {className: 'ContactItem-email'}, this.props.email),
13 | React.createElement('div', {className: 'ContactItem-description'}, this.props.description)
14 | )
15 | )
16 | },
17 | });
18 |
--------------------------------------------------------------------------------
/ContactsView.js:
--------------------------------------------------------------------------------
1 | var ContactsView = React.createClass({
2 | propTypes: {
3 | contacts: React.PropTypes.array.isRequired,
4 | newContact: React.PropTypes.object.isRequired,
5 | onNewContactChange: React.PropTypes.func.isRequired,
6 | onNewContactSubmit: React.PropTypes.func.isRequired,
7 | },
8 |
9 | render: function() {
10 | return (
11 | React.createElement('div', {className: 'ContactView'},
12 | React.createElement('h1', {className: 'ContactView-title'}, "Contacts"),
13 | React.createElement('ul', {className: 'ContactView-list'},
14 | this.props.contacts.map(function(contact) {
15 | return React.createElement(ContactItem, contact)
16 | })),
17 | React.createElement(ContactForm, {
18 | value: this.props.newContact,
19 | onChange: this.props.onNewContactChange,
20 | onSubmit: this.props.onNewContactSubmit,
21 | })
22 | )
23 | )
24 | },
25 | });
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Learn Raw React: Ridiculously Simple Forms
2 |
3 | This repository holds the solution for the final exercise of [Learn Raw React, part 2](http://jamesknelson.com/learn-raw-react-ridiculously-simple-forms/)
4 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | My CRM
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Constants
3 | */
4 |
5 |
6 | var CONTACT_TEMPLATE = {name: "", email: "", description: "", errors: null}
7 |
8 |
9 |
10 | /*
11 | * Model
12 | */
13 |
14 |
15 | // The app's complete current state
16 | var state = {};
17 |
18 | // Make the given changes to the state and perform any required housekeeping
19 | function setState(changes) {
20 | Object.assign(state, changes);
21 |
22 | ReactDOM.render(
23 | React.createElement(ContactsView, Object.assign({}, state, {
24 | onNewContactChange: updateNewContact,
25 | onNewContactSubmit: submitNewContact,
26 | })),
27 | document.getElementById('react-app')
28 | );
29 | }
30 |
31 | // Set initial data
32 | setState({
33 | contacts: [
34 | {key: 1, name: "James K Nelson", email: "james@jamesknelson.com", description: "Front-end Unicorn"},
35 | {key: 2, name: "Jim", email: "jim@example.com"},
36 | ],
37 | newContact: Object.assign({}, CONTACT_TEMPLATE),
38 | });
39 |
40 |
41 |
42 | /*
43 | * Actions
44 | */
45 |
46 |
47 | function updateNewContact(contact) {
48 | setState({ newContact: contact });
49 | }
50 |
51 |
52 | function submitNewContact() {
53 | var contact = Object.assign({}, state.newContact, {key: state.contacts.length + 1, errors: {}});
54 |
55 | if (!contact.name) {
56 | contact.errors.name = ["Please enter your new contact's name"]
57 | }
58 | if (!/.+@.+\..+/.test(contact.email)) {
59 | contact.errors.email = ["Please enter your new contact's email"]
60 | }
61 |
62 | setState(
63 | Object.keys(contact.errors).length === 0
64 | ? {
65 | newContact: Object.assign({}, CONTACT_TEMPLATE),
66 | contacts: state.contacts.slice(0).concat(contact),
67 | }
68 | : { newContact: contact }
69 | )
70 | }
71 |
--------------------------------------------------------------------------------
/polyfills.js:
--------------------------------------------------------------------------------
1 | // From https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
2 | if (!Object.assign) {
3 | Object.defineProperty(Object, 'assign', {
4 | enumerable: false,
5 | configurable: true,
6 | writable: true,
7 | value: function(target) {
8 | 'use strict';
9 | if (target === undefined || target === null) {
10 | throw new TypeError('Cannot convert first argument to object');
11 | }
12 |
13 | var to = Object(target);
14 | for (var i = 1; i < arguments.length; i++) {
15 | var nextSource = arguments[i];
16 | if (nextSource === undefined || nextSource === null) {
17 | continue;
18 | }
19 | nextSource = Object(nextSource);
20 |
21 | var keysArray = Object.keys(Object(nextSource));
22 | for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
23 | var nextKey = keysArray[nextIndex];
24 | var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
25 | if (desc !== undefined && desc.enumerable) {
26 | to[nextKey] = nextSource[nextKey];
27 | }
28 | }
29 | }
30 | return to;
31 | }
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: Tahoma, sans-serif;
3 | margin: 0;
4 | }
5 |
6 | .ContactView-title {
7 | font-size: 24px;
8 | padding: 0 24px;
9 | }
10 |
11 | .ContactView-list {
12 | list-style: none;
13 | margin: 0;
14 | padding: 0;
15 | border-top: 1px solid #f0f0f0;
16 | }
17 |
18 | .ContactItem {
19 | margin: 0;
20 | padding: 8px 24px;
21 | border-bottom: 1px solid #f0f0f0;
22 | }
23 | .ContactItem-name {
24 | font-size: 16px;
25 | font-weight: bold;
26 | margin: 0;
27 | }
28 | .ContactItem-email {
29 | font-size: 14px;
30 | margin-top: 4px;
31 | font-style: italic;
32 | color: #888;
33 | }
34 | .ContactItem-description {
35 | font-size: 14px;
36 | margin-top: 4px;
37 | }
38 |
39 |
40 | .ContactForm {
41 | padding: 8px 24px;
42 | }
43 | .ContactForm > input,
44 | .ContactForm > textarea {
45 | display: block;
46 | width: 240px;
47 | padding: 4px 8px;
48 | margin-bottom: 8px;
49 | border-radius: 3px;
50 | border: 1px solid #888;
51 | font-size: 14px;
52 | }
53 | .ContactForm > input.ContactForm-error {
54 | border-color: #b30e2f;
55 | }
56 |
--------------------------------------------------------------------------------