├── 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 | --------------------------------------------------------------------------------