diff --git a/playground/index.html b/playground/index.html index 9490b847fb..2a93441194 100644 --- a/playground/index.html +++ b/playground/index.html @@ -5,7 +5,7 @@ react-jsonschema-form playground - + diff --git a/playground/index.prod.html b/playground/index.prod.html index 996378073d..521805772a 100644 --- a/playground/index.prod.html +++ b/playground/index.prod.html @@ -5,7 +5,7 @@ react-jsonschema-form playground - + diff --git a/playground/samples/errors.js b/playground/samples/errors.js index 179c28918e..31d6363712 100644 --- a/playground/samples/errors.js +++ b/playground/samples/errors.js @@ -7,7 +7,8 @@ module.exports = { type: "string", title: "First name", minLength: 8, - pattern: "\\d+" + pattern: "\\d+", + "description": "Note: this field isn't required, but will be validated for length and pattern as soon as a value is entered.", }, active: { type: "boolean", diff --git a/src/components/widgets/AltDateWidget.js b/src/components/widgets/AltDateWidget.js index b977f7c9bd..ce50d037a6 100644 --- a/src/components/widgets/AltDateWidget.js +++ b/src/components/widgets/AltDateWidget.js @@ -21,6 +21,7 @@ function DateElement(props) { const {SelectWidget} = registry.widgets; return ( { - return props.onChange(value === "" ? undefined : value); + return onChange(value); }; - return ( + const input = ( onBlur(inputProps.id, event.target.value))}/> ); + return !clearable ? input : ( + + {input} + + ); } BaseInput.defaultProps = { type: "text", required: false, + clearable: true, disabled: false, readonly: false, autofocus: false, @@ -44,6 +60,7 @@ if (process.env.NODE_ENV !== "production") { placeholder: PropTypes.string, value: PropTypes.any, required: PropTypes.bool, + clearable: PropTypes.bool, disabled: PropTypes.bool, readonly: PropTypes.bool, autofocus: PropTypes.bool, diff --git a/src/components/widgets/CheckboxWidget.js b/src/components/widgets/CheckboxWidget.js index d65c663559..9beff28767 100644 --- a/src/components/widgets/CheckboxWidget.js +++ b/src/components/widgets/CheckboxWidget.js @@ -1,29 +1,40 @@ import React, {PropTypes} from "react"; +import ClearableWidget from "./ClearableWidget"; + function CheckboxWidget({ schema, id, value, required, + readonly, disabled, label, autofocus, onChange, }) { return ( -
- -
+ +
+
+ +
+
+
); } diff --git a/src/components/widgets/CheckboxesWidget.js b/src/components/widgets/CheckboxesWidget.js index d6471a409d..ea9d47ba78 100644 --- a/src/components/widgets/CheckboxesWidget.js +++ b/src/components/widgets/CheckboxesWidget.js @@ -1,5 +1,7 @@ import React, {PropTypes} from "react"; +import ClearableWidget from "./ClearableWidget"; + function selectValue(value, selected, all) { const at = all.indexOf(value); @@ -14,44 +16,51 @@ function deselectValue(value, selected) { } function CheckboxesWidget(props) { - const {id, disabled, options, value, autofocus, onChange} = props; + const {id, disabled, readonly, options, value, autofocus, onChange} = props; const {enumOptions, inline} = options; return ( -
{ - enumOptions.map((option, index) => { - const checked = value.indexOf(option.value) !== -1; - const disabledCls = disabled ? "disabled" : ""; - const checkbox = ( - - { - const all = enumOptions.map(({value}) => value); - if (event.target.checked) { - onChange(selectValue(option.value, value, all)); - } else { - onChange(deselectValue(option.value, value)); - } - }}/> - {option.label} - - ); - return inline ? ( - - ) : ( -
-
+ ) : ( +
+ +
+ ); + }) + }
+ ); } diff --git a/src/components/widgets/ClearableWidget.js b/src/components/widgets/ClearableWidget.js new file mode 100644 index 0000000000..f298f58326 --- /dev/null +++ b/src/components/widgets/ClearableWidget.js @@ -0,0 +1,45 @@ +import React, {PropTypes} from "react"; + + +function ClearableWidget({onChange, disabled, readonly, value, children}) { + const _onClear = (event) => { + event.preventDefault(); + if (typeof value !== "undefined") { + return onChange(undefined); + } + }; + const cleared = typeof value === "undefined"; + const clearBtnCls = "glyphicon glyphicon-remove-sign clear-btn"; + const clearBtnStyles = { + pointerEvents: "auto", + textDecoration: "none", + cursor: cleared ? "no-drop" : "pointer", + color: cleared ? "#aaa" : "#888", + }; + return disabled || readonly ? children : ( +
+ {children} +
+ +
+
+ ); +} + +ClearableWidget.defaultProps = { + disabled: false, + readonly: false, +}; + +if (process.env.NODE_ENV !== "production") { + ClearableWidget.propTypes = { + children: React.PropTypes.element.isRequired, + onChange: PropTypes.func, + value: PropTypes.any, + disabled: PropTypes.bool, + readonly: PropTypes.bool, + }; +} + +export default ClearableWidget; diff --git a/src/components/widgets/DateTimeWidget.js b/src/components/widgets/DateTimeWidget.js index 9f4ca1f45e..4fea301b34 100644 --- a/src/components/widgets/DateTimeWidget.js +++ b/src/components/widgets/DateTimeWidget.js @@ -15,10 +15,12 @@ function toJSONDate(dateString) { function DateTimeWidget(props) { const {value, onChange} = props; + // Note: native HTML date widgets are already clearable. return ( onChange(toJSONDate(value))}/> ); diff --git a/src/components/widgets/DateWidget.js b/src/components/widgets/DateWidget.js index 89b856e7a1..82e4a4d15c 100644 --- a/src/components/widgets/DateWidget.js +++ b/src/components/widgets/DateWidget.js @@ -5,10 +5,12 @@ import BaseInput from "./BaseInput"; function DateWidget(props) { const {onChange} = props; + // Note: native HTML date widgets are already clearable. return ( onChange(value || undefined)}/> ); } diff --git a/src/components/widgets/RadioWidget.js b/src/components/widgets/RadioWidget.js index 86a88b27b7..a7ff57d71f 100644 --- a/src/components/widgets/RadioWidget.js +++ b/src/components/widgets/RadioWidget.js @@ -1,5 +1,7 @@ import React, {PropTypes} from "react"; +import ClearableWidget from "./ClearableWidget"; + function RadioWidget({ schema, @@ -7,6 +9,7 @@ function RadioWidget({ value, required, disabled, + readonly, autofocus, onChange }) { @@ -16,37 +19,43 @@ function RadioWidget({ // checked={checked} has been moved above name={name}, As mentioned in #349; // this is a temporary fix for radio button rendering bug in React, facebook/react#7630. return ( -
{ - enumOptions.map((option, i) => { - const checked = option.value === value; - const disabledCls = disabled ? "disabled" : ""; - const radio = ( - - onChange(option.value)}/> - {option.label} - - ); + +
{ + enumOptions.map((option, i) => { + const checked = option.value === value; + const disabledCls = disabled ? "disabled" : ""; + const radio = ( + + onChange(option.value)}/> + {option.label} + + ); - return inline ? ( - - ) : ( -
-
- ); - }) - }
+ ) : ( +
+ +
+ ); + }) + }
+ ); } diff --git a/src/components/widgets/SelectWidget.js b/src/components/widgets/SelectWidget.js index 0595e5dff2..944a876efc 100644 --- a/src/components/widgets/SelectWidget.js +++ b/src/components/widgets/SelectWidget.js @@ -1,7 +1,9 @@ import React, {PropTypes} from "react"; +import ClearableWidget from "./ClearableWidget"; import {asNumber} from "../../utils"; + /** * This is a silly limitation in the DOM where option change event values are * always retrieved as strings. @@ -34,6 +36,7 @@ function SelectWidget({ id, options, value, + clearable, required, disabled, readonly, @@ -45,7 +48,11 @@ function SelectWidget({ }) { const {enumOptions} = options; const emptyValue = multiple ? [] : ""; - return ( + const _onChange = (event) => { + const newValue = getValue(event, multiple); + onChange(processValue(schema, newValue)); + }; + const select = ( ); + return !clearable ? select : ( + + {select} + + ); } SelectWidget.defaultProps = { autofocus: false, + clearable: true, }; if (process.env.NODE_ENV !== "production") { @@ -83,6 +97,7 @@ if (process.env.NODE_ENV !== "production") { enumOptions: PropTypes.array, }).isRequired, value: PropTypes.any, + clearable: PropTypes.bool, required: PropTypes.bool, multiple: PropTypes.bool, autofocus: PropTypes.bool, diff --git a/src/components/widgets/TextareaWidget.js b/src/components/widgets/TextareaWidget.js index 7bcb03b263..af893f5f67 100644 --- a/src/components/widgets/TextareaWidget.js +++ b/src/components/widgets/TextareaWidget.js @@ -1,5 +1,7 @@ import React, {PropTypes} from "react"; +import ClearableWidget from "./ClearableWidget"; + function TextareaWidget({ schema, @@ -14,20 +16,27 @@ function TextareaWidget({ onBlur }) { const _onChange = ({target: {value}}) => { - return onChange(value === "" ? undefined : value); + return onChange(value); }; + // Note: Textareas are always clearable. return ( -