Skip to content

Commit

Permalink
Merge pull request react-bootstrap#626 from aabenoja/submit-split
Browse files Browse the repository at this point in the history
Splitting Button, Reset, and Submit from Input
  • Loading branch information
mtscout6 committed May 14, 2015
2 parents cb1cc99 + 1e4cd18 commit 6f73149
Show file tree
Hide file tree
Showing 13 changed files with 425 additions and 243 deletions.
1 change: 1 addition & 0 deletions docs/examples/.eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"Badge",
"Button",
"ButtonGroup",
"ButtonInput",
"ButtonToolbar",
"CollapsibleNav",
"CollapsibleMixin",
Expand Down
44 changes: 44 additions & 0 deletions docs/examples/ButtonInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
const ButtonInputExample = React.createClass({
getInitialState() {
return {
disabled: true,
style: ''
};
},

resetValidation() {
this.setState({
disabled: true,
style: ''
});
},

validationState() {
let length = this.refs.input.getValue().length;
let style = 'danger';

if (length > 10) { style = 'success'; }
else if (length > 5) { style = 'warning'; }

let disabled = style !== 'success';

return { style, disabled };
},

handleChange() {
this.setState( this.validationState() );
},

render() {
return (
<form>
<Input type="text" ref="input" onChange={this.handleChange} />
<ButtonInput bsSize="small">Child Text</ButtonInput>
<ButtonInput type="reset" bsStyle="primary" onClick={this.resetValidation} />
<ButtonInput type="submit" value="Submit Your Input" bsStyle={this.state.style} bsSize="large" disabled={this.state.disabled} />
</form>
);
}
});

React.render(<ButtonInputExample />, mountNode);
4 changes: 3 additions & 1 deletion docs/examples/InputTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ const inputTypeInstance = (
</Input>
<Input type='textarea' label='Text Area' placeholder='textarea' />
<Input type='static' value='Static Text' />
<Input type='submit' value='Submit button' />
<ButtonInput value='Button Input' />
<ButtonInput type='reset' value='Reset Button' />
<ButtonInput type='submit' value='Submit Button' />
</form>
);

Expand Down
3 changes: 3 additions & 0 deletions docs/src/ComponentsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,9 @@ const ComponentsPage = React.createClass({
<h2 id='input-types'>Types</h2>
<p>Supports <code>select</code>, <code>textarea</code>, <code>static</code> as well as standard HTML input types. <code>getValue()</code> returns an array for multiple select.</p>
<ReactPlayground codeText={Samples.InputTypes} />
<h2 id='button-input-types'>Button Input Types</h2>
<p>Form buttons are encapsulated by <code>ButtonInput</code>. Pass in <code>type="reset"</code> or <code>type="submit"</code> to suit your needs. Styling is the same as <code>Button</code>.</p>
<ReactPlayground codeText={Samples.ButtonInput} />
<h2 id='input-addons'>Add-ons</h2>
<p>Use <code>addonBefore</code> and <code>addonAfter</code> for normal addons, <code>buttonBefore</code> and <code>buttonAfter</code> for button addons.
Exotic configurations may require some css on your side.</p>
Expand Down
2 changes: 2 additions & 0 deletions docs/src/ReactPlayground.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as modAlert from '../../src/Alert';
import * as modBadge from '../../src/Badge';
import * as modmodButton from '../../src/Button';
import * as modButtonGroup from '../../src/ButtonGroup';
import * as modButtonInput from '../../src/ButtonInput';
import * as modmodButtonToolbar from '../../src/ButtonToolbar';
import * as modCollapsibleNav from '../../src/CollapsibleNav';
import * as modCollapsibleMixin from '../../src/CollapsibleMixin';
Expand Down Expand Up @@ -52,6 +53,7 @@ const Alert = modAlert.default;
const Badge = modBadge.default;
const Button = modmodButton.default;
const ButtonGroup = modButtonGroup.default;
const ButtonInput = modButtonInput.default;
const ButtonToolbar = modmodButtonToolbar.default;
const CollapsibleNav = modCollapsibleNav.default;
const CollapsibleMixin = modCollapsibleMixin.default;
Expand Down
1 change: 1 addition & 0 deletions docs/src/Samples.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export default {
TableResponsive: require('fs').readFileSync(__dirname + '/../examples/TableResponsive.js', 'utf8'),
Input: require('fs').readFileSync(__dirname + '/../examples/Input.js', 'utf8'),
InputTypes: require('fs').readFileSync(__dirname + '/../examples/InputTypes.js', 'utf8'),
ButtonInput: require('fs').readFileSync(__dirname + '/../examples/ButtonInput.js', 'utf8'),
InputAddons: require('fs').readFileSync(__dirname + '/../examples/InputAddons.js', 'utf8'),
InputSizes: require('fs').readFileSync(__dirname + '/../examples/InputSizes.js', 'utf8'),
InputValidation: require('fs').readFileSync(__dirname + '/../examples/InputValidation.js', 'utf8'),
Expand Down
40 changes: 40 additions & 0 deletions src/ButtonInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import Button from './Button';
import FormGroup from './FormGroup';
import InputBase from './InputBase';

function valueValidation({children, value}, propName, componentName) {
if (children && value) {
return new Error('Both value and children cannot be passed to ButtonInput');
}
return React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.number]).call(null, {children, value}, propName, componentName);
}

class ButtonInput extends InputBase {
renderFormGroup(children) {
let {bsStyle, value, ...other} = this.props; /* eslint no-unused-vars: 0 object-shorthand: 0 */
return <FormGroup {...other}>{children}</FormGroup>;
}

renderInput() {
let {children, value, ...other} = this.props;
let val = children ? children : value;
return <Button {...other} componentClass="input" ref="input" key="input" value={val} />;
}
}

ButtonInput.defaultProps = {
type: 'button'
};

ButtonInput.propTypes = {
type: React.PropTypes.oneOf(['button', 'reset', 'submit']),
bsStyle(props) {
//defer to Button propTypes of bsStyle
return null;
},
children: valueValidation,
value: valueValidation
};

export default ButtonInput;
240 changes: 10 additions & 230 deletions src/Input.js
Original file line number Diff line number Diff line change
@@ -1,239 +1,19 @@
import React from 'react';
import classNames from 'classnames';
import Button from './Button';
import FormGroup from './FormGroup';
import InputBase from './InputBase';
import ButtonInput from './ButtonInput';
import deprecationWarning from './utils/deprecationWarning';

class Input extends React.Component {
getInputDOMNode() {
return React.findDOMNode(this.refs.input);
}

getValue() {
if (this.props.type === 'static') {
return this.props.value;
} else if (this.props.type) {
if (this.props.type === 'select' && this.props.multiple) {
return this.getSelectedOptions();
} else {
return this.getInputDOMNode().value;
}
} else {
throw 'Cannot use getValue without specifying input type.';
}
}

getChecked() {
return this.getInputDOMNode().checked;
}

getSelectedOptions() {
let values = [];

Array.prototype.forEach.call(
this.getInputDOMNode().getElementsByTagName('option'),
(option) => {
if (option.selected) {
let value = option.getAttribute('value') || option.innerHtml;
values.push(value);
}
});

return values;
}

isCheckboxOrRadio() {
return this.props.type === 'checkbox' || this.props.type === 'radio';
}

isFile() {
return this.props.type === 'file';
}

renderInputGroup(children) {
let addonBefore = this.props.addonBefore ? (
<span className="input-group-addon" key="addonBefore">
{this.props.addonBefore}
</span>
) : null;

let addonAfter = this.props.addonAfter ? (
<span className="input-group-addon" key="addonAfter">
{this.props.addonAfter}
</span>
) : null;

let buttonBefore = this.props.buttonBefore ? (
<span className="input-group-btn">
{this.props.buttonBefore}
</span>
) : null;

let buttonAfter = this.props.buttonAfter ? (
<span className="input-group-btn">
{this.props.buttonAfter}
</span>
) : null;

let inputGroupClassName;
switch (this.props.bsSize) {
case 'small': inputGroupClassName = 'input-group-sm'; break;
case 'large': inputGroupClassName = 'input-group-lg'; break;
}

return addonBefore || addonAfter || buttonBefore || buttonAfter ? (
<div className={classNames(inputGroupClassName, 'input-group')} key="input-group">
{addonBefore}
{buttonBefore}
{children}
{addonAfter}
{buttonAfter}
</div>
) : children;
}

renderIcon() {
let classes = {
'glyphicon': true,
'form-control-feedback': true,
'glyphicon-ok': this.props.bsStyle === 'success',
'glyphicon-warning-sign': this.props.bsStyle === 'warning',
'glyphicon-remove': this.props.bsStyle === 'error'
};

return this.props.hasFeedback ? (
<span className={classNames(classes)} key="icon" />
) : null;
}
const buttonTypes = ['button', 'reset', 'submit'];

renderHelp() {
return this.props.help ? (
<span className="help-block" key="help">
{this.props.help}
</span>
) : null;
}

renderCheckboxAndRadioWrapper(children) {
let classes = {
'checkbox': this.props.type === 'checkbox',
'radio': this.props.type === 'radio'
};

return (
<div className={classNames(classes)} key="checkboxRadioWrapper">
{children}
</div>
);
}

renderWrapper(children) {
return this.props.wrapperClassName ? (
<div className={this.props.wrapperClassName} key="wrapper">
{children}
</div>
) : children;
}

renderLabel(children) {
let classes = {
'control-label': !this.isCheckboxOrRadio()
};
classes[this.props.labelClassName] = this.props.labelClassName;

return this.props.label ? (
<label htmlFor={this.props.id} className={classNames(classes)} key="label">
{children}
{this.props.label}
</label>
) : children;
}

renderInput() {
if (!this.props.type) {
return this.props.children;
}

switch (this.props.type) {
case 'select':
return (
<select {...this.props} className={classNames(this.props.className, 'form-control')} ref="input" key="input">
{this.props.children}
</select>
);
case 'textarea':
return <textarea {...this.props} className={classNames(this.props.className, 'form-control')} ref="input" key="input" />;
case 'static':
return (
<p {...this.props} className={classNames(this.props.className, 'form-control-static')} ref="input" key="input">
{this.props.value}
</p>
);
case 'submit':
return <Button {...this.props} componentClass="input" ref="input" key="input" />;
}

let className = this.isCheckboxOrRadio() || this.isFile() ? '' : 'form-control';
return <input {...this.props} className={classNames(this.props.className, className)} ref="input" key="input" />;
}

renderFormGroup(children) {
if (this.props.type === 'submit') {
let {bsStyle, ...other} = this.props; /* eslint no-unused-vars: 0 object-shorthand: 0 */
return <FormGroup {...other}>{children}</FormGroup>;
class Input extends InputBase {
render() {
if (buttonTypes.indexOf(this.props.type) > -1) {
deprecationWarning(`Input type=${this.props.type}`, 'ButtonInput');
return <ButtonInput {...this.props} />;
}

return <FormGroup {...this.props}>{children}</FormGroup>;
}

renderChildren() {
return !this.isCheckboxOrRadio() ? [
this.renderLabel(),
this.renderWrapper([
this.renderInputGroup(
this.renderInput()
),
this.renderIcon(),
this.renderHelp()
])
] : this.renderWrapper([
this.renderCheckboxAndRadioWrapper(
this.renderLabel(
this.renderInput()
)
),
this.renderHelp()
]);
}

render() {
let children = this.renderChildren();
return this.renderFormGroup(children);
return super.render();
}
}

Input.propTypes = {
type: React.PropTypes.string,
label: React.PropTypes.node,
help: React.PropTypes.node,
addonBefore: React.PropTypes.node,
addonAfter: React.PropTypes.node,
buttonBefore: React.PropTypes.node,
buttonAfter: React.PropTypes.node,
bsSize: React.PropTypes.oneOf(['small', 'medium', 'large']),
bsStyle(props) {
if (props.type === 'submit') {
return null;
}
return React.PropTypes.oneOf(['success', 'warning', 'error']).apply(null, arguments);
},
hasFeedback: React.PropTypes.bool,
id: React.PropTypes.string,
groupClassName: React.PropTypes.string,
wrapperClassName: React.PropTypes.string,
labelClassName: React.PropTypes.string,
multiple: React.PropTypes.bool,
disabled: React.PropTypes.bool,
value: React.PropTypes.any
};

export default Input;
Loading

0 comments on commit 6f73149

Please sign in to comment.