├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── __tests__ ├── form.test.js ├── input.test.js └── inputs.test.js ├── example ├── app.js ├── example.js ├── index.html └── package.json ├── index.js ├── lib ├── react-form.js └── react-form │ ├── addon-mixin.js │ ├── form.js │ ├── input.js │ ├── inputs.js │ └── path-mixin.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .module-cache/ 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | before_install: 5 | - "npm config set spin false" 6 | - "npm install -g jest" 7 | script: 8 | - "npm test" 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015 korbin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-form 2 | [![npm version](https://badge.fury.io/js/react-form.svg)](http://badge.fury.io/js/react-form) 3 | [![Build Status](https://travis-ci.org/korbin/react-form.svg?branch=master)](https://travis-ci.org/korbin/react-form) 4 | 5 | ## Synopsis 6 | **react-form** is a lightweight, opinionated form-building system for [React](https://github.com/facebook/react). The react-form DSL was designed to produce clean code that directly resembles an underlying object hierarchy. 7 | 8 | ## Design Goals 9 | - Consistent, clean, fun-to-use DSL. 10 | - Server-side rendering is assumed. 11 | - Input components are pluggable. 12 | - HTTP POST backward compatibility. 13 | 14 | ## Quick Start 15 | Install react-form, react-form-inputs: 16 | ```Shell 17 | npm install --save react-form react-form-inputs 18 | ``` 19 | Basic usage example (JSX+ES6): 20 | 21 | ```JavaScript 22 | var React = require('react'); 23 | var {Form, Input} = require('react-form'); 24 | var {Password, Text} = require('react-form-inputs'); 25 | 26 | var LoginForm = React.createClass({ 27 | render() { 28 | return ( 29 |
30 |

Login

31 | 32 | 33 | 34 |
35 | ); 36 | } 37 | }); 38 | 39 | var user = { 40 | username: null, 41 | password: null 42 | }; 43 | 44 | React.render(, document.getElementById('login')); 45 | ``` 46 | 47 | react-form ships with an example application - try it out: 48 | 49 | ```Shell 50 | cd example 51 | npm install 52 | npm link ../ 53 | node app 54 | #Open http://localhost:3000 55 | ``` 56 | 57 | ## Technical 58 | ### Object mapping 59 | react-form uses a context-based materialized path system to track and propagate object changes. 60 | 61 | The *Form*, *Inputs*, and *Input* components expose interfaces to easily reference nodes anywhere in an object hierarchy. Each of the aforementioned components represents a "path segment." 62 | 63 | Each of these components exposes 64 | 65 | ### Components 66 | #### Form 67 | Form is a required outer container component. Form components are responsible for maintaining state and propagating changes. Form exposes event interfaces for advanced integration with a parent application, Store, component, etc. 68 | 69 | Form has several configurable props: 70 | - **for** `String` - Namespace for a Form instance. Used to generate input names. Defaults to "object." 71 | - **object** `Object` - Object used to hydrate input fields. Defaults to "{}." 72 | - **onSubmit** `Function(event)` - Called when Form instance is submitted. 73 | - **onUpdate** `Function(path, value)` - Called when a nested Input at *path* undergoes a value change to *value*. NOTE: Supplying this handler disables internal Form state management; onUpdate must replace props.object to trigger a rerender. 74 | - Unmentioned props are applied to the resulting form tag. 75 | 76 | **Tip:** When implementing a custom onUpdate or onSubmit handler, use objectPath and 77 | this.getValue() to negotiate object changes. 78 | 79 | #### Inputs 80 | Inputs is a nested container element. Inputs is required to reference nested objects and arrays of objects. **Inputs is not utilized in simple, single-tier models.** 81 | 82 | When referencing an Array of objects, all children are duplicated and mapped to each array element. 83 | 84 | Inputs has two user-configurable props: 85 | - **for** `String` - Required path segment. Must reference an object or array of objects. 86 | - **forName** `String` - Optionally used to override "name" attributes of nested components. 87 | 88 | Example: 89 | ```JavaScript 90 | var {Form, Inputs} = require('react-form'); 91 | var gizmo = { //path: [] 92 | widgets: [ //path: [widgets] <-- INPUTS 93 | { color: "Red" }, //path: [widgets, 0, color] 94 | { color: "Blue" } //path: [widgets, 1, color] 95 | //... 96 |
97 | //in this example, children will be duplicated twice 98 | ``` 99 | 100 | #### Input 101 | Input is the most commonly-used component. Input is used to render form controls, each varying in function and complexity. 102 | 103 | Input requires API-compliant, pluggable components known as "types." For flexibility, no Input types ship with react-form. A number of useful, common components can be found in [react-form-inputs](https://github.com/korbin/react-form-inputs). 104 | 105 | Input has three universally-applicable props: 106 | - **type** `Input type` - Required, API-compliant component to render. 107 | - **for** `String` - Required path segment. Must reference a value appropriate for given Input type. 108 | - **forName** `String` - Optionally used to override generated Input "name." 109 | 110 | See type-specific documentation for respective Input types for additional, configurable props. 111 | 112 | Example: 113 | ```JavaScript 114 | var React = require('react'); 115 | var {Form} = require('react-form'); 116 | var Text = require('react-form-inputs/text'); 117 | 118 | var gizmo = { //path: [] 119 | name: "Foo" //path: [name] 120 | //... 121 | 122 | //Initial value of "Foo" 123 | ``` 124 | 125 | ### Addons 126 | Documentation regarding the built-in addon system will be provided in an upcoming release. Optional addons enabling conformity with Rails-conventions, field label generation, and error handling are in development. 127 | 128 | ## Developing 129 | Presently, react-form APIs are highly unstable, untested, and explosive! 130 | 131 | Run tests: 132 | ```Shell 133 | npm test 134 | ``` 135 | 136 | Link a dependent project: 137 | ```Shell 138 | #Within dependent project directory... 139 | npm link ~/path-to-react-form/ 140 | ``` 141 | 142 | ## Contributing 143 | - [Fork](https://github.com/korbin/react-form/fork) the project. 144 | - Create a descriptively-named branch for your changes. *(fix_whatever, add_this)* 145 | - Commit your change. 146 | - Add appropriate documentation, test coverage. 147 | - Test with "npm test" (requires jest-cli). 148 | - Issue a [pull request](https://github.com/korbin/react-form/pulls) for your branch. 149 | 150 | ## Thanks 151 | The react-form DSL was **heavily** inspired by (stolen from) [Formtastic](https://github.com/justinfrench/formtastic). 152 | 153 | ## License 154 | react-form is released under the [MIT License](LICENSE). 155 | -------------------------------------------------------------------------------- /__tests__/form.test.js: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | describe('Form', function() { 3 | var Form = require('../lib/react-form/form'); 4 | var React = require('react/addons'); 5 | var TestUtils = React.addons.TestUtils; 6 | 7 | var formObject = { 8 | bar: 'baz' 9 | } 10 | 11 | var formProps = { 12 | for: 'foo', 13 | object: formObject 14 | }; 15 | var mockChild = React.createClass({ 16 | contextTypes: Form.childContextTypes, 17 | render: function() { 18 | return React.DOM.div(); 19 | } 20 | }); 21 | 22 | 23 | it('passes context to children', function() { 24 | var form = TestUtils.renderIntoDocument(React.createElement(Form, formProps)); 25 | 26 | var childContext = form.getChildContext(); 27 | 28 | expect(childContext.object).toEqual(formObject); 29 | expect(childContext.namePath.toArray()).toEqual([formProps.for]); 30 | expect(childContext.valuePath.toArray()).toEqual([]); 31 | expect(childContext.setPathValue).toBeDefined(); 32 | }); 33 | 34 | it('respects override of onSubmit handler', function() { 35 | var mockOnSubmit = jest.genMockFn(); 36 | 37 | var form = TestUtils.renderIntoDocument(React.createElement(Form, {onSubmit: mockOnSubmit})); 38 | form._handleSubmit(); 39 | 40 | expect(mockOnSubmit).toBeCalled(); 41 | }); 42 | 43 | it('calls onUpdate if present', function() { 44 | var mockOnUpdate = jest.genMockFn(); 45 | 46 | var form = TestUtils.renderIntoDocument(React.createElement(Form, {onUpdate: mockOnUpdate})); 47 | form._handleUpdate('path', 'value'); 48 | 49 | expect(mockOnUpdate).toBeCalledWith('path', 'value'); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /__tests__/input.test.js: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | describe('Input', function() { 3 | var Input = require('../lib/react-form/input'); 4 | var Immutable = require('immutable'); 5 | var React = require('react/addons'); 6 | var TestUtils = React.addons.TestUtils; 7 | 8 | var ctx = { 9 | object: {foo: 'bar'}, 10 | namePath: Immutable.List(), 11 | valuePath: Immutable.List(), 12 | setPathValue: function(){} 13 | }; 14 | 15 | var mockComponentRendered = jest.genMockFn(); 16 | 17 | var mockComponentClass = React.createClass({ 18 | render: function() { 19 | mockComponentRendered(); 20 | return React.DOM.div(); 21 | } 22 | }); 23 | 24 | var input = React.withContext(ctx, function() { 25 | return React.createElement(Input, {for:'foo', type: mockComponentClass}); 26 | }); 27 | 28 | it('renders a component with given type class', function() { 29 | var rendered = TestUtils.renderIntoDocument(input); 30 | 31 | expect(mockComponentRendered).toBeCalled(); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /__tests__/inputs.test.js: -------------------------------------------------------------------------------- 1 | jest.autoMockOff(); 2 | describe('Inputs', function() { 3 | var Inputs = require('../lib/react-form/inputs'); 4 | var Immutable = require('immutable'); 5 | var React = require('react/addons'); 6 | var TestUtils = React.addons.TestUtils; 7 | 8 | var ctx = { 9 | object: {foo: [{}, {}]}, 10 | namePath: Immutable.List(), 11 | valuePath: Immutable.List(), 12 | setPathValue: function(){} 13 | }; 14 | 15 | var inputs = React.withContext(ctx, function() { 16 | return React.createElement(Inputs, {for:'foo'}, React.DOM.div({className:'mock'})); 17 | }); 18 | 19 | it('duplicates children for each object array item', function() { 20 | var rendered = TestUtils.renderIntoDocument(inputs); 21 | 22 | expect(TestUtils.scryRenderedDOMComponentsWithClass(rendered, 'mock').length).toEqual(2); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /example/app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var browserify = require('browserify-middleware'); 3 | var exposify = require('exposify'); 4 | 5 | exposify.config = {react: 'React', 'react/addons': 'React'}; 6 | 7 | var app = express(); 8 | 9 | browserify.settings({ 10 | transform: [ 11 | ['reactify', {es6: true, sourceMaps: true}], 12 | [exposify, {global:true}] 13 | ] 14 | }); 15 | 16 | browserify.settings.development({ 17 | cache: 'dynamic', 18 | gzip: true, 19 | precompile: true 20 | }); 21 | 22 | app.all('/', function(req, res) { 23 | res.sendFile('./index.html', {root: __dirname}); 24 | }); 25 | 26 | app.get('/example.js', browserify('./example.js')); 27 | 28 | app.listen(3000); 29 | -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | var objectPath = require('object-path'); 2 | var React = require('react/addons'); 3 | var rf = require('react-form'); 4 | rfi = require('react-form-inputs'); 5 | 6 | var gizmo = { 7 | 'name':'foo', 8 | 'description': 'Hello world!\nHow are you?', 9 | 'owner':{ 10 | 'name':'Foo Barrington' 11 | }, 12 | 'widgets':[ 13 | { 14 | 'name':'Baz', 15 | 'type': 'Knickknack', 16 | 'color': 'Red' 17 | }, 18 | { 19 | 'name':'Qux' 20 | } 21 | ] 22 | }; 23 | 24 | var handleSubmit = function(e) { 25 | e.preventDefault(); 26 | console.log('Captured submit for form, object:', this.getValue()); 27 | }; 28 | 29 | var handleUpdate = function(path, value) { 30 | console.log('Captured update for path: "' + path.toArray() + '", value: "' + value + '"'); 31 | objectPath.set(gizmo, path.toArray(), value); 32 | React.render(, document.getElementById('example')); 33 | }; 34 | 35 | var newWidget = function() { 36 | gizmo.widgets.push({}); 37 | React.render(, document.getElementById('example')); 38 | console.log('Blank widget added'); 39 | }; 40 | 41 | var Example = React.createClass({ 42 | render() { 43 | return ( 44 | 45 |

Edit Gizmo

46 | 47 | 48 | 49 | 50 |

Owner

51 | 52 |
53 |
54 | 55 | 56 |

Widget

57 | 58 | 59 | 60 |
61 |
62 | 63 | 64 | 65 |
66 | ); 67 | } 68 | }); 69 | 70 | React.render(, document.getElementById('example')); 71 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | KForm Demo 7 | 8 | 9 | 10 | 18 | 19 | 20 |
21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-form-example", 3 | "main": "app.js", 4 | "dependencies": { 5 | "browserify-middleware": "git+ssh://git@github.com:korbin/browserify-middleware.git", 6 | "browserify-shim": "^3.8.1", 7 | "exposify": "^0.2.0", 8 | "express": "^4.10.6", 9 | "react-form-inputs": "0.0.1", 10 | "reactify": "^0.17.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/react-form'); 2 | -------------------------------------------------------------------------------- /lib/react-form.js: -------------------------------------------------------------------------------- 1 | /** 2 | * react-form 3 | * 4 | * @license MIT 5 | * @link https://github.com/korbin/react-form 6 | */ 7 | 8 | exports.Form = require('./react-form/form'); 9 | exports.Input = require('./react-form/input'); 10 | exports.Inputs = require('./react-form/inputs'); 11 | -------------------------------------------------------------------------------- /lib/react-form/addon-mixin.js: -------------------------------------------------------------------------------- 1 | var compose = require('lodash.compose'); 2 | var Immutable = require('immutable'); 3 | var React = require('react'); 4 | 5 | var AddonMixin = function(reactClass) { 6 | return { 7 | contextTypes: { 8 | addons: React.PropTypes.instanceOf(Immutable.Set) 9 | }, 10 | 11 | childContextTypes: { 12 | addons: React.PropTypes.instanceOf(Immutable.Set).isRequired 13 | }, 14 | 15 | getChildContext: function() { 16 | return { 17 | addons: this._getAddons() 18 | }; 19 | }, 20 | 21 | render: function() { 22 | return this._installAddons(this.renderWithAddons()); 23 | }, 24 | 25 | _getAddons: function() { 26 | return Immutable.Set() 27 | .union(this.context.addons || []) 28 | .union(this.props.addons || []); 29 | }, 30 | 31 | _getInstallers: function() { 32 | var installers = []; 33 | 34 | this._getAddons().forEach(function(addon) { 35 | var installer = addon['installOn'+reactClass]; 36 | 37 | if (installer) { 38 | installers.push(installer.bind(this)); 39 | } 40 | }, this); 41 | 42 | return installers; 43 | }, 44 | 45 | _installAddons: function(render) { 46 | var installers = this._getInstallers(); 47 | 48 | if (installers.length === 0) { 49 | return render; 50 | } 51 | 52 | return compose.apply(this, installers)(render); 53 | } 54 | }; 55 | }; 56 | 57 | module.exports = AddonMixin; 58 | -------------------------------------------------------------------------------- /lib/react-form/form.js: -------------------------------------------------------------------------------- 1 | var Immutable = require('immutable'); 2 | var objectPath = require('object-path'); 3 | var React = require('react'); 4 | 5 | var AddonMixin = require('./addon-mixin'); 6 | var PathMixin = require('./path-mixin'); 7 | 8 | var Form = React.createClass({ 9 | displayName: 'Form', 10 | 11 | mixins: [AddonMixin('Form')], 12 | 13 | propTypes: { 14 | for: React.PropTypes.string, 15 | object: React.PropTypes.object 16 | }, 17 | 18 | getDefaultProps: function() { 19 | return { 20 | for: 'object', 21 | object: {} 22 | }; 23 | }, 24 | 25 | getInitialState: function() { 26 | return {object: this.props.object}; 27 | }, 28 | 29 | componentWillReceiveProps: function(nextProps) { 30 | this.setState({object: nextProps.object}); 31 | }, 32 | 33 | childContextTypes: PathMixin.contextTypes, 34 | 35 | getChildContext: function() { 36 | return { 37 | object: this.state.object, 38 | namePath: Immutable.List([this.props.for]), 39 | valuePath: Immutable.List(), 40 | setPathValue: this._handleUpdate 41 | }; 42 | }, 43 | 44 | getValue: function() { 45 | return this.state.object; 46 | }, 47 | 48 | renderWithAddons: function() { 49 | //Create className for container. 50 | var cn = ['form', this.props.for].join(' ').trim(); 51 | 52 | var props = React.__spread({}, this.props, { 53 | className: cn, 54 | for: undefined, 55 | onSubmit: this._handleSubmit, 56 | onUpdate: this._handleUpdate 57 | }); 58 | 59 | //Child cloning is required for child context propagation - React bug? 60 | return React.DOM.form(props, React.Children.map(this.props.children, React.addons.cloneWithProps, this)); 61 | }, 62 | 63 | _handleSubmit: function(e) { 64 | if (this.props.onSubmit) { 65 | this.props.onSubmit.call(this, e); 66 | return; 67 | } 68 | }, 69 | 70 | _handleUpdate: function(path, value) { 71 | if (this.props.onUpdate) { 72 | this.props.onUpdate.call(this, path, value); 73 | return; 74 | } 75 | 76 | //TODO(korbin): Write immutable objectPath library. 77 | var object = React.__spread({}, this.state.object); 78 | objectPath.set(object, path.toArray(), value); 79 | this.setState({object: object}); 80 | } 81 | }); 82 | 83 | module.exports = Form; 84 | -------------------------------------------------------------------------------- /lib/react-form/input.js: -------------------------------------------------------------------------------- 1 | var React = require('react'); 2 | 3 | var AddonMixin = require('./addon-mixin'); 4 | var PathMixin = require('./path-mixin'); 5 | 6 | var Input = React.createClass({ 7 | displayName: 'Input', 8 | 9 | mixins: [PathMixin, AddonMixin('Input')], 10 | 11 | propTypes: { 12 | type: React.PropTypes.func.isRequired 13 | }, 14 | 15 | renderWithAddons: function() { 16 | 17 | var props = React.__spread({}, this.props, { 18 | _pathSegment: false, 19 | getId: this.getId, 20 | getName: this.getName, 21 | getValue: this.getValue, 22 | setValue: this.setValue, 23 | type: undefined, 24 | for: undefined 25 | }); 26 | 27 | //Create className for container. 28 | var cn = [ 29 | 'field', 30 | this.getNamePathSegment() 31 | ]; 32 | 33 | if (this.props.type.displayName) { 34 | cn.push(this.props.type.displayName.toLowerCase()); 35 | } 36 | 37 | return React.DOM.div({className: cn.join(' ')}, React.createElement(this.props.type, props)); 38 | }, 39 | 40 | setValue: function() { 41 | this.context.setPathValue.apply(this, [this.getValuePath()].concat(Array.prototype.slice.call(arguments))); 42 | } 43 | }); 44 | 45 | module.exports = Input; 46 | -------------------------------------------------------------------------------- /lib/react-form/inputs.js: -------------------------------------------------------------------------------- 1 | /** @jsx React.DOM */ 2 | var React = require('react/addons'); 3 | 4 | var AddonMixin = require('./addon-mixin'); 5 | var PathMixin = require('./path-mixin'); 6 | 7 | var Inputs = React.createClass({ 8 | displayName: 'Inputs', 9 | 10 | mixins: [PathMixin, AddonMixin('Inputs')], 11 | 12 | renderWithAddons: function() { 13 | //Create className for container. 14 | var cn = [ 15 | 'fields', 16 | this.getNamePathSegment() 17 | ]; 18 | 19 | return React.DOM.div({className: cn.join(' ')}, this._renderChildren()); 20 | }, 21 | 22 | _renderChildren: function() { 23 | if (Array.isArray(this.getValue())) { 24 | return this.getValue().map(function(_, i) { return React.createElement(Inputs, {for: '' + i, key: i}, this.props.children); }, this); 25 | } 26 | 27 | return React.Children.map(this.props.children, React.addons.cloneWithProps); 28 | } 29 | }); 30 | 31 | module.exports = Inputs; 32 | -------------------------------------------------------------------------------- /lib/react-form/path-mixin.js: -------------------------------------------------------------------------------- 1 | var Immutable = require('immutable'); 2 | var objectPath = require('object-path'); 3 | var React = require('react'); 4 | var type = require('type-detect'); 5 | 6 | var PathMixin = { 7 | propTypes: { 8 | for: React.PropTypes.string.isRequired, 9 | forName: React.PropTypes.string, 10 | _pathSegment: React.PropTypes.bool 11 | }, 12 | 13 | getDefaultProps: function() { 14 | return {_pathSegment: true}; 15 | }, 16 | 17 | contextTypes: { 18 | object: React.PropTypes.any.isRequired, 19 | namePath: React.PropTypes.instanceOf(Immutable.List).isRequired, 20 | valuePath: React.PropTypes.instanceOf(Immutable.List).isRequired, 21 | setPathValue: React.PropTypes.func.isRequired 22 | }, 23 | 24 | childContextTypes: { 25 | namePath: React.PropTypes.instanceOf(Immutable.List).isRequired, 26 | valuePath: React.PropTypes.instanceOf(Immutable.List).isRequired 27 | }, 28 | 29 | getChildContext: function() { 30 | return { 31 | namePath: this.getNamePath(), 32 | valuePath: this.getValuePath() 33 | }; 34 | }, 35 | 36 | getId: function() { 37 | if (this.props.id) { 38 | return this.props.id; 39 | } 40 | 41 | if (this.props.getId) { 42 | return this.props.getId.call(this); 43 | } 44 | 45 | if (this.context.getId) { 46 | return this.context.getId.call(this); 47 | } 48 | 49 | return this.getNamePath().toArray().join('_'); 50 | }, 51 | 52 | getName: function() { 53 | if (this.props.name) { 54 | return this.props.name; 55 | } 56 | 57 | if (this.props.getName) { 58 | return this.props.getName.call(this); 59 | } 60 | 61 | if (this.context.getName) { 62 | return this.context.getName.call(this); 63 | } 64 | 65 | return this.getNamePath().toArray().join('.'); 66 | }, 67 | 68 | getNamePath: function() { 69 | if (this.props._pathSegment) { 70 | return (this.context.namePath || this.context.valuePath).push(this.props.forName || this.props.for); 71 | } 72 | 73 | return this.context.namePath; 74 | }, 75 | 76 | getNamePathSegment: function() { 77 | return this.getNamePath().toArray().slice(-1)[0]; 78 | }, 79 | 80 | getValue: function() { 81 | if (type(this.props.value) !== 'undefined') { 82 | return this.props.value; 83 | } 84 | 85 | return objectPath.get(this.context.object, this.getValuePath().toArray()); 86 | }, 87 | 88 | getValuePath: function() { 89 | if (this.props._pathSegment) { 90 | return this.context.valuePath.push(this.props.for); 91 | } 92 | 93 | return this.context.valuePath; 94 | } 95 | }; 96 | 97 | module.exports = PathMixin; 98 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-form", 3 | "version": "0.0.2", 4 | "description": "A modular React form system.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/korbin/react-form" 12 | }, 13 | "keywords": [ 14 | "react", 15 | "form", 16 | "builder", 17 | "rails", 18 | "formtastic" 19 | ], 20 | "author": "korbin ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/korbin/react-form/issues" 24 | }, 25 | "homepage": "https://github.com/korbin/react-form", 26 | "dependencies": { 27 | "immutable": "^3.3.0", 28 | "lodash.compose": "^2.4.1", 29 | "object-path": "^0.8.1", 30 | "react": "^0.12.1", 31 | "type-detect": "^0.1.2" 32 | }, 33 | "devDependencies": { 34 | "jest-cli": "^0.2.1" 35 | }, 36 | "jest": { 37 | "unmockedModulePathPatterns": [ 38 | "./node_modules/react" 39 | ] 40 | } 41 | } 42 | --------------------------------------------------------------------------------