Skip to content

Commit

Permalink
Onfocus support (rjsf-team#657)
Browse files Browse the repository at this point in the history
  • Loading branch information
mauriciominella authored and n1k0 committed Aug 11, 2017
1 parent 9d79b71 commit 40fc2fd
Show file tree
Hide file tree
Showing 15 changed files with 136 additions and 0 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ If you plan on being notified everytime the form data are updated, you can pass

Sometimes you may want to trigger events or modify external state when a field has been touched, so you can pass an `onBlur` handler, which will receive the id of the input that was blurred and the field value.

#### Form field focus events

Sometimes you may want to trigger events or modify external state when a field has been focused, so you can pass an `onFocus` handler, which will receive the id of the input that is focused and the field value.

## Form customization

### The `uiSchema` object
Expand Down Expand Up @@ -954,6 +958,7 @@ The following props are passed to custom widget components:
- `readonly`: `true` if the widget is read-only;
- `onChange`: The value change event handler; call it with the new value everytime it changes;
- `onBlur`: The input blur event handler; call it with the the widget id and value;
- `onFocus`: The input focus event handler; call it with the the widget id and value;
- `options`: A map of options passed as a prop to the component (see [Custom widget options](#custom-widget-options)).
- `formContext`: The `formContext` object that you passed to Form.

Expand Down
2 changes: 2 additions & 0 deletions playground/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,8 @@ class App extends Component {
validate={validate}
onBlur={(id, value) =>
console.log(`Touched ${id} with value ${value}`)}
onFocus={(id, value) =>
console.log(`Focused ${id} with value ${value}`)}
transformErrors={transformErrors}
onError={log("errors")}>
<div className="row">
Expand Down
7 changes: 7 additions & 0 deletions src/components/Form.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ export default class Form extends Component {
}
};

onFocus = (...args) => {
if (this.props.onFocus) {
this.props.onFocus(...args);
}
};

onSubmit = event => {
event.preventDefault();
this.setState({ status: "submitted" });
Expand Down Expand Up @@ -195,6 +201,7 @@ export default class Form extends Component {
formData={formData}
onChange={this.onChange}
onBlur={this.onBlur}
onFocus={this.onFocus}
registry={registry}
safeRenderCompletion={safeRenderCompletion}
/>
Expand Down
11 changes: 11 additions & 0 deletions src/components/fields/ArrayField.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ class ArrayField extends Component {
registry = getDefaultRegistry(),
formContext,
onBlur,
onFocus,
} = this.props;
const title = schema.title === undefined ? name : schema.title;
const { ArrayFieldTemplate, definitions, fields } = registry;
Expand All @@ -326,6 +327,7 @@ class ArrayField extends Component {
itemUiSchema: uiSchema.items,
autofocus: autofocus && index === 0,
onBlur,
onFocus,
});
}),
className: `field field-array field-array-of-${itemsSchema.type}`,
Expand Down Expand Up @@ -357,6 +359,7 @@ class ArrayField extends Component {
readonly,
autofocus,
onBlur,
onFocus,
registry = getDefaultRegistry(),
} = this.props;
const items = this.props.formData;
Expand All @@ -374,6 +377,7 @@ class ArrayField extends Component {
multiple
onChange={this.onSelectChange}
onBlur={onBlur}
onFocus={onFocus}
options={options}
schema={schema}
value={items}
Expand All @@ -395,6 +399,7 @@ class ArrayField extends Component {
readonly,
autofocus,
onBlur,
onFocus,
registry = getDefaultRegistry(),
} = this.props;
const title = schema.title || name;
Expand All @@ -409,6 +414,7 @@ class ArrayField extends Component {
multiple
onChange={this.onSelectChange}
onBlur={onBlur}
onFocus={onFocus}
schema={schema}
title={title}
value={items}
Expand All @@ -433,6 +439,7 @@ class ArrayField extends Component {
autofocus,
registry = getDefaultRegistry(),
onBlur,
onFocus,
} = this.props;
const title = schema.title || name;
let items = this.props.formData;
Expand Down Expand Up @@ -481,6 +488,7 @@ class ArrayField extends Component {
itemErrorSchema,
autofocus: autofocus && index === 0,
onBlur,
onFocus,
});
}),
onAddClick: this.onAddClick,
Expand Down Expand Up @@ -510,6 +518,7 @@ class ArrayField extends Component {
itemErrorSchema,
autofocus,
onBlur,
onFocus,
} = props;
const {
disabled,
Expand Down Expand Up @@ -541,6 +550,7 @@ class ArrayField extends Component {
required={this.isItemRequired(itemSchema)}
onChange={this.onChangeForIndex(index)}
onBlur={onBlur}
onFocus={onFocus}
registry={this.props.registry}
disabled={this.props.disabled}
readonly={this.props.readonly}
Expand Down Expand Up @@ -592,6 +602,7 @@ if (process.env.NODE_ENV !== "production") {
errorSchema: PropTypes.object,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
formData: PropTypes.array,
required: PropTypes.bool,
disabled: PropTypes.bool,
Expand Down
2 changes: 2 additions & 0 deletions src/components/fields/ObjectField.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class ObjectField extends Component {
disabled,
readonly,
onBlur,
onFocus,
registry = getDefaultRegistry(),
} = this.props;
const { definitions, fields, formContext } = registry;
Expand Down Expand Up @@ -94,6 +95,7 @@ class ObjectField extends Component {
formData={formData[name]}
onChange={this.onPropertyChange(name)}
onBlur={onBlur}
onFocus={onFocus}
registry={registry}
disabled={disabled}
readonly={readonly}
Expand Down
3 changes: 3 additions & 0 deletions src/components/fields/StringField.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function StringField(props) {
autofocus,
onChange,
onBlur,
onFocus,
registry = getDefaultRegistry(),
} = props;
const { title, format } = schema;
Expand All @@ -42,6 +43,7 @@ function StringField(props) {
value={formData}
onChange={onChange}
onBlur={onBlur}
onFocus={onFocus}
required={required}
disabled={disabled}
readonly={readonly}
Expand All @@ -60,6 +62,7 @@ if (process.env.NODE_ENV !== "production") {
idSchema: PropTypes.object,
onChange: PropTypes.func.isRequired,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
formData: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
registry: PropTypes.shape({
widgets: PropTypes.objectOf(
Expand Down
3 changes: 3 additions & 0 deletions src/components/widgets/BaseInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ function BaseInput(props) {
disabled,
autofocus,
onBlur,
onFocus,
options,
schema,
formContext,
Expand All @@ -31,6 +32,7 @@ function BaseInput(props) {
{...inputProps}
onChange={_onChange}
onBlur={onBlur && (event => onBlur(inputProps.id, event.target.value))}
onFocus={onFocus && (event => onFocus(inputProps.id, event.target.value))}
/>
);
}
Expand All @@ -54,6 +56,7 @@ if (process.env.NODE_ENV !== "production") {
autofocus: PropTypes.bool,
onChange: PropTypes.func,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
};
}

Expand Down
9 changes: 9 additions & 0 deletions src/components/widgets/SelectWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ function SelectWidget(props) {
autofocus,
onChange,
onBlur,
onFocus,
placeholder,
} = props;
const { enumOptions } = options;
Expand All @@ -68,6 +69,13 @@ function SelectWidget(props) {
onBlur(id, processValue(schema, newValue));
})
}
onFocus={
onFocus &&
(event => {
const newValue = getValue(event, multiple);
onFocus(id, processValue(schema, newValue));
})
}
onChange={event => {
const newValue = getValue(event, multiple);
onChange(processValue(schema, newValue));
Expand Down Expand Up @@ -107,6 +115,7 @@ if (process.env.NODE_ENV !== "production") {
autofocus: PropTypes.bool,
onChange: PropTypes.func,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
};
}

Expand Down
3 changes: 3 additions & 0 deletions src/components/widgets/TextareaWidget.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ function TextareaWidget(props) {
autofocus,
onChange,
onBlur,
onFocus,
} = props;
const _onChange = ({ target: { value } }) => {
return onChange(value === "" ? options.emptyValue : value);
Expand All @@ -29,6 +30,7 @@ function TextareaWidget(props) {
autoFocus={autofocus}
rows={options.rows}
onBlur={onBlur && (event => onBlur(id, event.target.value))}
onFocus={onFocus && (event => onFocus(id, event.target.value))}
onChange={_onChange}
/>
);
Expand All @@ -54,6 +56,7 @@ if (process.env.NODE_ENV !== "production") {
autofocus: PropTypes.bool,
onChange: PropTypes.func,
onBlur: PropTypes.func,
onFocus: PropTypes.func,
};
}

Expand Down
18 changes: 18 additions & 0 deletions test/ArrayField_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -618,6 +618,24 @@ describe("ArrayField", () => {
expect(onBlur.calledWith(select.id, ["foo", "bar"])).to.be.true;
});

it("should handle a focus event", () => {
const onFocus = sandbox.spy();
const { node } = createFormComponent({ schema, onFocus });

const select = node.querySelector(".field select");
Simulate.focus(select, {
target: {
options: [
{ selected: true, value: "foo" },
{ selected: true, value: "bar" },
{ selected: false, value: "fuzz" },
],
},
});

expect(onFocus.calledWith(select.id, ["foo", "bar"])).to.be.true;
});

it("should fill field with data", () => {
const { node } = createFormComponent({
schema,
Expand Down
25 changes: 25 additions & 0 deletions test/Form_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,31 @@ describe("Form", () => {
});
});

describe("Focus handler", () => {
it("should call provided focus handler on form input focus event", () => {
const schema = {
type: "object",
properties: {
foo: {
type: "string",
},
},
};
const formData = {
foo: "",
};
const onFocus = sandbox.spy();
const { node } = createFormComponent({ schema, formData, onFocus });

const input = node.querySelector("[type=text]");
Simulate.focus(input, {
target: { value: "new" },
});

sinon.assert.calledWithMatch(onFocus, input.id, "new");
});
});

describe("Error handler", () => {
it("should call provided error handler on validation errors", () => {
const schema = {
Expand Down
18 changes: 18 additions & 0 deletions test/NumberField_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,24 @@ describe("NumberField", () => {

expect(onBlur.calledWith(input.id, 2));
});

it("should handle a focus event", () => {
const onFocus = sandbox.spy();
const { node } = createFormComponent({
schema: {
type: "number",
},
onFocus,
});

const input = node.querySelector("input");
Simulate.focus(input, {
target: { value: "2" },
});

expect(onFocus.calledWith(input.id, 2));
});

it("should fill field with data", () => {
const { node } = createFormComponent({
schema: {
Expand Down
12 changes: 12 additions & 0 deletions test/ObjectField_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,18 @@ describe("ObjectField", () => {
expect(onBlur.calledWith(input.id, "changed")).to.be.true;
});

it("should handle object fields with focus events", () => {
const onFocus = sandbox.spy();
const { node } = createFormComponent({ schema, onFocus });

const input = node.querySelector("input[type=text]");
Simulate.focus(input, {
target: { value: "changed" },
});

expect(onFocus.calledWith(input.id, "changed")).to.be.true;
});

it("should render the widget with the expected id", () => {
const { node } = createFormComponent({ schema });

Expand Down
16 changes: 16 additions & 0 deletions test/StringField_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,22 @@ describe("StringField", () => {
expect(onBlur.calledWith(input.id, "yo")).to.be.true;
});

it("should handle a focus event", () => {
const onFocus = sandbox.spy();
const { node } = createFormComponent({
schema: {
type: "string",
},
onFocus,
});
const input = node.querySelector("input");
Simulate.focus(input, {
target: { value: "yo" },
});

expect(onFocus.calledWith(input.id, "yo")).to.be.true;
});

it("should handle an empty string change event", () => {
const { comp, node } = createFormComponent({
schema: { type: "string" },
Expand Down
2 changes: 2 additions & 0 deletions test/performance_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ describe("Rendering performance optimizations", () => {
describe("SchemaField", () => {
const onChange = () => {};
const onBlur = () => {};
const onFocus = () => {};
const registry = getDefaultRegistry();
const uiSchema = {};
const schema = {
Expand All @@ -123,6 +124,7 @@ describe("Rendering performance optimizations", () => {
onChange,
idSchema,
onBlur,
onFocus,
};

const { comp } = createComponent(SchemaField, props);
Expand Down

0 comments on commit 40fc2fd

Please sign in to comment.