diff --git a/src/ButtonInput.js b/src/ButtonInput.js
new file mode 100644
index 0000000000..846d577ed4
--- /dev/null
+++ b/src/ButtonInput.js
@@ -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 {children};
+ }
+
+ renderInput() {
+ let {children, value, ...other} = this.props;
+ let val = children ? children : value;
+ return ;
+ }
+}
+
+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;
diff --git a/src/Input.js b/src/Input.js
index d0870481cc..61456c70c3 100644
--- a/src/Input.js
+++ b/src/Input.js
@@ -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 ? (
-
- {this.props.addonBefore}
-
- ) : null;
-
- let addonAfter = this.props.addonAfter ? (
-
- {this.props.addonAfter}
-
- ) : null;
-
- let buttonBefore = this.props.buttonBefore ? (
-
- {this.props.buttonBefore}
-
- ) : null;
-
- let buttonAfter = this.props.buttonAfter ? (
-
- {this.props.buttonAfter}
-
- ) : 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 ? (
-
- {addonBefore}
- {buttonBefore}
- {children}
- {addonAfter}
- {buttonAfter}
-
- ) : 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 ? (
-
- ) : null;
- }
+const buttonTypes = ['button', 'reset', 'submit'];
- renderHelp() {
- return this.props.help ? (
-
- {this.props.help}
-
- ) : null;
- }
-
- renderCheckboxAndRadioWrapper(children) {
- let classes = {
- 'checkbox': this.props.type === 'checkbox',
- 'radio': this.props.type === 'radio'
- };
-
- return (
-
- {children}
-
- );
- }
-
- renderWrapper(children) {
- return this.props.wrapperClassName ? (
-
- {children}
-
- ) : children;
- }
-
- renderLabel(children) {
- let classes = {
- 'control-label': !this.isCheckboxOrRadio()
- };
- classes[this.props.labelClassName] = this.props.labelClassName;
-
- return this.props.label ? (
-
- ) : children;
- }
-
- renderInput() {
- if (!this.props.type) {
- return this.props.children;
- }
-
- switch (this.props.type) {
- case 'select':
- return (
-
- );
- case 'textarea':
- return ;
- case 'static':
- return (
-
- {this.props.value}
-
- );
- case 'submit':
- return ;
- }
-
- let className = this.isCheckboxOrRadio() || this.isFile() ? '' : 'form-control';
- return ;
- }
-
- renderFormGroup(children) {
- if (this.props.type === 'submit') {
- let {bsStyle, ...other} = this.props; /* eslint no-unused-vars: 0 object-shorthand: 0 */
- return {children};
+class Input extends InputBase {
+ render() {
+ if (buttonTypes.indexOf(this.props.type) > -1) {
+ deprecationWarning(`Input type=${this.props.type}`, 'ButtonInput');
+ return ;
}
- return {children};
- }
-
- 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;
diff --git a/src/InputBase.js b/src/InputBase.js
new file mode 100644
index 0000000000..c552a7a6ab
--- /dev/null
+++ b/src/InputBase.js
@@ -0,0 +1,226 @@
+import React from 'react';
+import classNames from 'classnames';
+import FormGroup from './FormGroup';
+
+class InputBase 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 ? (
+
+ {this.props.addonBefore}
+
+ ) : null;
+
+ let addonAfter = this.props.addonAfter ? (
+
+ {this.props.addonAfter}
+
+ ) : null;
+
+ let buttonBefore = this.props.buttonBefore ? (
+
+ {this.props.buttonBefore}
+
+ ) : null;
+
+ let buttonAfter = this.props.buttonAfter ? (
+
+ {this.props.buttonAfter}
+
+ ) : 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 ? (
+
+ {addonBefore}
+ {buttonBefore}
+ {children}
+ {addonAfter}
+ {buttonAfter}
+
+ ) : 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 ? (
+
+ ) : null;
+ }
+
+ renderHelp() {
+ return this.props.help ? (
+
+ {this.props.help}
+
+ ) : null;
+ }
+
+ renderCheckboxAndRadioWrapper(children) {
+ let classes = {
+ 'checkbox': this.props.type === 'checkbox',
+ 'radio': this.props.type === 'radio'
+ };
+
+ return (
+
+ {children}
+
+ );
+ }
+
+ renderWrapper(children) {
+ return this.props.wrapperClassName ? (
+
+ {children}
+
+ ) : children;
+ }
+
+ renderLabel(children) {
+ let classes = {
+ 'control-label': !this.isCheckboxOrRadio()
+ };
+ classes[this.props.labelClassName] = this.props.labelClassName;
+
+ return this.props.label ? (
+
+ ) : children;
+ }
+
+ renderInput() {
+ if (!this.props.type) {
+ return this.props.children;
+ }
+
+ switch (this.props.type) {
+ case 'select':
+ return (
+
+ );
+ case 'textarea':
+ return ;
+ case 'static':
+ return (
+
+ {this.props.value}
+
+ );
+ }
+
+ let className = this.isCheckboxOrRadio() || this.isFile() ? '' : 'form-control';
+ return ;
+ }
+
+ renderFormGroup(children) {
+ return {children};
+ }
+
+ 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);
+ }
+}
+
+InputBase.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: React.PropTypes.oneOf(['success', 'warning', 'error']),
+ 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 InputBase;
diff --git a/test/ButtonInputSpec.js b/test/ButtonInputSpec.js
new file mode 100644
index 0000000000..5118181938
--- /dev/null
+++ b/test/ButtonInputSpec.js
@@ -0,0 +1,82 @@
+import React from 'react';
+import ReactTestUtils from 'react/lib/ReactTestUtils';
+import ButtonInput from '../src/ButtonInput';
+import {shouldWarn} from './helpers';
+
+describe('ButtonInput', () =>{
+ it('renders an input button element with type=button', function () {
+ const instance = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ const node = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'input').getDOMNode();
+ assert.equal(node.getAttribute('type'), 'button');
+ assert.equal(node.getAttribute('class'), 'btn btn-danger');
+ });
+
+ it('supports type=reset and type=submit', function () {
+ let instance = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ let node = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'input').getDOMNode();
+ assert.equal(node.getAttribute('type'), 'reset');
+
+ instance = ReactTestUtils.renderIntoDocument(
+
+ );
+
+ node = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'input').getDOMNode();
+ assert.equal(node.getAttribute('type'), 'submit');
+ });
+
+ it('throws warning about unsupported type', function () {
+ ReactTestUtils.renderIntoDocument(
+
+ );
+
+ shouldWarn('propType: Invalid');
+ });
+
+ it('must not throw warning when bsStyle=danger', function () {
+ ReactTestUtils.renderIntoDocument(
+
+ );
+
+ console.warn.called.should.be.false;
+ });
+
+ it('throws warning about wrong type for bsStyle=error', function () {
+ ReactTestUtils.renderIntoDocument(
+
+ );
+
+ shouldWarn('propType: Invalid');
+ });
+
+ it('throws a warning if given both children and a value property', function () {
+ ReactTestUtils.renderIntoDocument(
+ button
+ );
+
+ shouldWarn('Both value and children');
+ });
+
+ it('does not throw an error for strings and numbers', function () {
+ let testData = { children: 'EUREKA' };
+ let result = ButtonInput.propTypes.children(testData, 'children', 'ButtonInput');
+ assert.notInstanceOf(result, Error);
+
+ testData = { value: 4 };
+ result = ButtonInput.propTypes.value(testData, 'children', 'ButtonInput');
+ assert.notInstanceOf(result, Error);
+ });
+
+ it('does not allow elements for children', function () {
+ ReactTestUtils.renderIntoDocument(
+ blah
+ );
+
+ shouldWarn('propType: Invalid');
+ });
+});
diff --git a/test/InputSpec.js b/test/InputSpec.js
index ed1cbf6467..065ff1824c 100644
--- a/test/InputSpec.js
+++ b/test/InputSpec.js
@@ -41,30 +41,28 @@ describe('Input', function () {
assert.equal(instance.getValue(), 'v');
});
- it('renders a submit button element when type=submit', function () {
- let instance = ReactTestUtils.renderIntoDocument(
-
+ it('throws a deprecation warning on type=button', function () {
+ ReactTestUtils.renderIntoDocument(
+
);
- let node = ReactTestUtils.findRenderedDOMComponentWithTag(instance, 'input').getDOMNode();
- assert.equal(node.getAttribute('type'), 'submit');
- assert.equal(node.getAttribute('class'), 'btn btn-danger');
+ shouldWarn('deprecated');
});
- it('must not throw warning when bsStyle=danger and type=submit', function () {
+ it('throws a deprecation warning on type=reset', function () {
ReactTestUtils.renderIntoDocument(
-
+
);
- console.warn.called.should.be.false;
+ shouldWarn('deprecated');
});
- it('throws warning about wrong type for bsStyle=error when type=submit', function () {
+ it('throws a deprecation warning on type=submit', function () {
ReactTestUtils.renderIntoDocument(
-
+
);
- shouldWarn('propType: Invalid');
+ shouldWarn('deprecated');
});
it('renders a p element when type=static', function () {