Add Autofill to data entry flow (#1528)

Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Steve Repsher 2022-11-15 13:59:11 -05:00 committed by GitHub
parent f2fd582ba1
commit e95eef1f04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -82,16 +82,22 @@ If the result type is `FlowResultType.ABORT`, the result should look like:
## Flow Handler ## Flow Handler
Flow handlers will handle a single flow. A flow contains one or more steps. When a flow is instantiated, the `FlowHandler.init_step` step will be called. Each step has three different possible results: "Show Form", "Abort" and "Create Entry". Flow handlers will handle a single flow. A flow contains one or more steps. When a flow is instantiated, the `FlowHandler.init_step` step will be called. Each step has several possible results:
- [Show Form](#show-form)
- [Create Entry](#create-entry)
- [Abort](#abort)
- [External Step](#external-step--external-step-done)
- [Show Progress](#show-progress--show-progress-done)
- [Show Menu](#show-menu)
At a minimum, each flow handler will have to define a version number and a step. This doesn't have to be `init`, as `async_create_flow` can assign `init_step` dependent on the current workflow, for example in configuration, `context.source` will be used as `init_step`. At a minimum, each flow handler will have to define a version number and a step. This doesn't have to be `init`, as `async_create_flow` can assign `init_step` dependent on the current workflow, for example in configuration, `context.source` will be used as `init_step`.
The bare minimum config flow: For example, a bare minimum config flow would be:
```python ```python
from homeassistant import data_entry_flow from homeassistant import data_entry_flow
@config_entries.HANDLERS.register(DOMAIN) @config_entries.HANDLERS.register(DOMAIN)
class ExampleConfigFlow(data_entry_flow.FlowHandler): class ExampleConfigFlow(data_entry_flow.FlowHandler):
@ -104,9 +110,13 @@ class ExampleConfigFlow(data_entry_flow.FlowHandler):
"""Handle user step.""" """Handle user step."""
``` ```
Data entry flows depend on translations for showing the text in the steps. It depends on the parent of a data entry flow manager where this is stored. For config and option flows, this is in `strings.json` under `config` and `option`, respectively.
For a more detailed explanation of `strings.json` see the [backend translation](/docs/internationalization/core) page.
### Show Form ### Show Form
This result type will show a form to the user to fill in. You define the current step, the schema of the data (using voluptuous or selectors) and optionally a dictionary of errors. This result type will show a form to the user to fill in. You define the current step, the schema of the data (using a mixture of voluptuous and/or [selectors](https://www.home-assistant.io/docs/blueprint/selectors/)) and optionally a dictionary of errors.
```python ```python
from homeassistant.helpers.selector import selector from homeassistant.helpers.selector import selector
@ -129,6 +139,77 @@ class ExampleConfigFlow(data_entry_flow.FlowHandler):
return self.async_show_form(step_id="init", data_schema=vol.Schema(data_schema)) return self.async_show_form(step_id="init", data_schema=vol.Schema(data_schema))
``` ```
#### Labels & Descriptions
Translations for the form are added to `strings.json` in a key for the `step_id`. That object may contain the folowing keys:
| Key | Value | Notes |
| :----------------: | :----------------: | :------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | Form heading | Do not include your brand name. It will be automatically injected from your manifest. |
| `description` | Form instructions | Optional. Do not link to the documentation as that is linked automatically. Do not include "basic" information like "Here you can set up X". |
| `data` | Field labels | Keep succinct and consistent with other integrations whenever appropriate for the best user experience. |
| `data_description` | Field descriptions | Optional explanatory text to show below the field. |
The field labels and descriptions are given as a dictionary with keys corresponding to your schema. Here is a simple example:
```json
{
"config": {
"step": {
"user": {
"title": "Add Group",
"description": "Some description",
"data": {
"entities": "Entities",
},
"data_description": {
"entities": "The entities to add to the group",
},
}
}
}
}
```
#### Enabling Browser Autofill
Suppose your integration is collecting form data which can be automatically filled by browsers or password managers, such as login credentials or contact information. You should enable autofill whenever possible for the best user experience and accessibility. There are two options to enable this.
The first option is to use Voluptuous with data keys recognized by the frontend. The frontend will recognize the keys `"username"` and `"password"` and add HTML `autocomplete` attribute values of `"username"` and `"current-password"` respectively. Support for autocomplete is limited to `"username"` and `"password"` fields and is supported primarily to quickly enable auto-fill on the many integrations that collect them without converting their schemas to selectors.
The second option is to use a [text selector](https://www.home-assistant.io/docs/blueprint/selectors/#text-selector). A text selector gives full control of the input type and allows any permitted value for `autocomplete` to be specified. A hypothetical schema collecting specific fillable data might be:
```python
import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
TextSelectorType,
)
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): TextSelector(
TextSelectorConfig(type=TextSelectorType.EMAIL, autocomplete="username")
),
vol.Required(CONF_PASSWORD): TextSelector(
TextSelectorConfig(
type=TextSelectorType.PASSWORD, autocomplete="current-password"
)
),
vol.Required("postal_code"): TextSelector(
TextSelectorConfig(type=TextSelectorType.TEXT, autocomplete="postal-code")
),
vol.Required("mobile_number"): TextSelector(
TextSelectorConfig(type=TextSelectorType.TEL, autocomplete="tel")
),
}
)
```
#### Defaults & Suggestions
If you'd like to pre-fill data in the form, you have two options. The first is to use the `default` parameter. This will both pre-fill the field, and act as the default value in case the user leaves the field empty. If you'd like to pre-fill data in the form, you have two options. The first is to use the `default` parameter. This will both pre-fill the field, and act as the default value in case the user leaves the field empty.
```python ```python
@ -149,7 +230,7 @@ The other alternative is to use a suggested value - this will also pre-fill the
You can also mix and match - pre-fill through `suggested_value`, and use a different value for `default` in case the field is left empty, but that could be confusing to the user so use carefully. You can also mix and match - pre-fill through `suggested_value`, and use a different value for `default` in case the field is left empty, but that could be confusing to the user so use carefully.
Title and description of the step will be provided via the translation file. Where this is defined depends on the context of the data entry flow. #### Validation
After the user has filled in the form, the step method will be called again and the user input is passed in. Your step will only be called if the user input passes your data schema. When the user passes in data, you will have to do extra validation of the data. For example, you can verify that the passed in username and password are valid. After the user has filled in the form, the step method will be called again and the user input is passed in. Your step will only be called if the user input passes your data schema. When the user passes in data, you will have to do extra validation of the data. For example, you can verify that the passed in username and password are valid.
@ -179,35 +260,9 @@ class ExampleConfigFlow(data_entry_flow.FlowHandler):
) )
``` ```
Translations for the step title, description and fields is added to `strings.json`. Each field can also have an optional entry in `data_description` to add extra explanatory text.
Do not put your brand title in the `title`. It will be automatically injected from your manifest.
Your description should not link to the documentation as that is linked automatically. It should also not contain "basic" information like "Here you can set up X". It can be omitted.
```json
{
"config": {
"step": {
"user": {
"title": "Add Group",
"description": "Some description",
"data": {
"entities": "Entities",
},
"data_description": {
"entities": "The entities to add to the group",
},
}
}
}
}
```
#### Multi-step flows #### Multi-step flows
If the user input passes validation, you can again return one of the three return values. If you want to navigate the user to the next step, return the return value of that step: If the user input passes validation, you can return one of the possible step types again. If you want to navigate the user to the next step, return the return value of that step:
```python ```python
class ExampleConfigFlow(data_entry_flow.FlowHandler): class ExampleConfigFlow(data_entry_flow.FlowHandler):
@ -274,7 +329,6 @@ Example configuration flow that includes an external step.
```python ```python
from homeassistant import config_entries from homeassistant import config_entries
@config_entries.HANDLERS.register(DOMAIN) @config_entries.HANDLERS.register(DOMAIN)
class ExampleConfigFlow(data_entry_flow.FlowHandler): class ExampleConfigFlow(data_entry_flow.FlowHandler):
VERSION = 1 VERSION = 1
@ -313,7 +367,7 @@ async def handle_result(hass, flow_id, data):
return "Invalid config flow specified" return "Invalid config flow specified"
``` ```
### Show Progress and Show Progress Done ### Show Progress & Show Progress Done
It is possible that we need the user to wait for a task that takes several minutes. It is possible that we need the user to wait for a task that takes several minutes.
@ -332,10 +386,8 @@ Example configuration flow that includes two show progress tasks.
```python ```python
from homeassistant import config_entries from homeassistant import config_entries
from .const import DOMAIN from .const import DOMAIN
class TestFlow(config_entries.ConfigFlow, domain=DOMAIN): class TestFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1 VERSION = 1
task_one = None task_one = None
@ -414,12 +466,6 @@ class ExampleConfigFlow(data_entry_flow.FlowHandler):
} }
``` ```
## Translations
Data entry flows depend on translations for showing the text in the forms. It depends on the parent of a data entry flow manager where this is stored. For config and option flows this is in `strings.json` under `config` and `option`.
For a more detailed explanation of `strings.json` see to the [backend translation](/docs/internationalization/core) page.
## Initializing a config flow from an external source ## Initializing a config flow from an external source
You might want to initialize a config flow programmatically. For example, if we discover a device on the network that requires user interaction to finish setup. To do so, pass a source parameter and optional user input when initializing a flow: You might want to initialize a config flow programmatically. For example, if we discover a device on the network that requires user interaction to finish setup. To do so, pass a source parameter and optional user input when initializing a flow: