-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from Narodni-repozitar/mirekys/fe-11-create-dev…
…eloper-docs-for-deposits [mirekys/FE-11] documentation on forms usage & customization
- Loading branch information
Showing
12 changed files
with
8,177 additions
and
8,831 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
16 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
{} | ||
{ | ||
"workbench.colorCustomizations": { | ||
"editorInfo.foreground": "#0080ff6a" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
--- | ||
sidebar_label: Form config | ||
sidebar_position: 3 | ||
--- | ||
|
||
# Customizing form config | ||
|
||
You can stitch together multiple backend-generated form configuration pieces | ||
by registering ServiceComponent classes in your UIResourceConfig class | ||
like this: | ||
|
||
```python | ||
from oarepo_ui.resources import BabelComponent | ||
from oarepo_vocabularies.ui.resources.components import DepositVocabularyOptionsComponent | ||
|
||
|
||
class MyAppUIResourceConfig(RecordsUIResourceConfig): | ||
components = [BabelComponent, DepositVocabularyOptionsComponent] | ||
``` | ||
|
||
In the example above, each class adds some block of config values, | ||
`BabelComponent` enriches `formConfig` with locale-related values of: | ||
|
||
```python | ||
current_locale='...', | ||
locales=[ | ||
{"value": '...', "text": '...'}, | ||
{"value": '...', "text": '...'}, | ||
{"value": '...', "text": '...'} | ||
], | ||
default_locale='...', | ||
``` | ||
|
||
and `DepositVocabularyOptionsComponent` will further add settings & dumped options for | ||
any Vocabularies you might have installed, configured & imported. | ||
|
||
```python | ||
vocabularies={ | ||
languages: [ | ||
{"value": '...', "text": '...'}, | ||
{"value": '...', "text": '...'} | ||
], | ||
institutions: [ | ||
{"value": '...', "text": '...'}, | ||
{"value": '...', "text": '...'} | ||
] | ||
} | ||
``` | ||
|
||
## Custom form config components | ||
|
||
You can also create your own form config component class, with the following function signature: | ||
|
||
```python | ||
class DepositVocabularyOptionsComponent(ServiceComponent): | ||
def form_config( | ||
self, | ||
*, | ||
form_config, | ||
resource, | ||
record, | ||
view_args, | ||
identity, | ||
layout, | ||
data, | ||
args, | ||
ui_links, | ||
extra_context, | ||
**kwargs | ||
): | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# Forms | ||
|
||
## Intended audience | ||
|
||
This section is intended for developers that need to create or customize existing deposition (record creation & edit) forms in their repository instance. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,202 @@ | ||
--- | ||
sidebar_position: 1 | ||
--- | ||
# Overview | ||
|
||
## [OARepo UI](https://github.com/oarepo/oarepo-ui) library | ||
|
||
To use and customize forms in your repository site, you will need have this python package in your project dependencies and | ||
extend the features provided by that library in your own UI app. | ||
|
||
This library provides the following basic support for forms. | ||
|
||
### UI routes | ||
|
||
Two form-related routes are provided by `RecordsUIResourceConfig`. One for creation of new records and one for editing existing records. | ||
|
||
```python | ||
class RecordsUIResourceConfig(UIResourceConfig): | ||
routes = { | ||
"create": "/_new", | ||
"edit": "/<pid_value>/edit", | ||
#... | ||
} | ||
#... | ||
``` | ||
|
||
### Form config | ||
|
||
UI resource config class provides an extensible `form_config` function, | ||
responsible for generation of React Form app runtime configuration. | ||
|
||
```python | ||
class RecordsUIResourceConfig: | ||
def form_config(self, identity=None, **kwargs): | ||
#... | ||
``` | ||
|
||
The default config structure looks like this, but can be further customized in multiple ways described | ||
in [Customizing form config](./form-config). | ||
|
||
```python | ||
dict( | ||
custom_fields=dict(), | ||
createUrl='/api/records/' | ||
**kwargs, | ||
) | ||
|
||
``` | ||
|
||
### UI resource views | ||
|
||
Resource views for both form contexts (`create` vs. `edit`) are very similar. It generates | ||
form config, determines which layout template to render, invokes all registered resource `components`, | ||
that implements `before_ui_create` or `before_ui_edit`, and finally renders the template with the | ||
following Jinja context. | ||
|
||
```python | ||
@login_required | ||
@request_read_args | ||
@request_view_args | ||
def create|edit(self): | ||
#... | ||
form_config = self.config.form_config( | ||
identity=g.identity, | ||
updateUrl=record.links.get("self", None) | ||
) | ||
|
||
self.run_components( | ||
"before_ui_create|edit", | ||
layout=layout, | ||
resource=self, | ||
record=record, | ||
data=record, | ||
form_config=form_config, | ||
args=resource_requestctx.args, | ||
view_args=resource_requestctx.view_args, | ||
identity=g.identity, | ||
extra_context=extra_context, | ||
) | ||
template_def = self.get_template_def("create|edit") | ||
template = current_oarepo_ui.get_template( | ||
template_def["layout"], template_def.get("blocks", {}) | ||
) | ||
|
||
return render_template( | ||
template, | ||
record=record, | ||
data=record, | ||
ui=record.get("ui", record), | ||
ui_config=self.config, | ||
ui_resource=self, | ||
layout=layout, | ||
component_key="create|edit", | ||
form_config=form_config, | ||
extra_context=extra_context, | ||
``` | ||
|
||
The only major difference is, that for `create` context, an `empty_record`: | ||
|
||
```python | ||
empty_record = self.empty_record(resource_requestctx) | ||
``` | ||
|
||
is being used as `record` data. This function creates an empty record according | ||
to its metadata structure, with all its values left blank. | ||
|
||
In `edit` context, both raw `record` metadata **and** `ui` serialized record | ||
representation is passed to form template context. | ||
|
||
### Jinja templates | ||
|
||
The `routes` from above are tied via Flask Blueprint with `resource views` (and `templates`), that renders the base `form.html` Jinja2 template. | ||
|
||
```python | ||
|
||
class RecordsUIResourceConfig(UIResourceConfig): | ||
templates = { | ||
"create": {"layout": "oarepo_ui/form.html"}, | ||
"edit": {"layout": "oarepo_ui/form.html"}, | ||
#... | ||
} | ||
#... | ||
} | ||
``` | ||
|
||
This template provides a basic mount point for React form apps, and multiple hidden inputs, | ||
feeding backend form configuration to the React form apps. | ||
|
||
A condensed version with just the extensibility-point block definitions looks like this: | ||
|
||
```python | ||
{% extends config.BASE_TEMPLATE %} | ||
|
||
{%- block javascript %} | ||
{{ super() }} | ||
{# {{ webpack['your-formapp-entrypoint-here.js'] }} #} | ||
{%- endblock javascript -%} | ||
|
||
{%- if form_config.createUrl %} | ||
{%- set title = _("New item") %} | ||
{% elif record.title %} | ||
{%- set title = _("Edit item ") + record.title %} | ||
{%- endif %} | ||
|
||
{%- block page_body %} | ||
{%- block form_main_content %} | ||
<input id="record" type="hidden" name="record" value='{{data | tojson }}' /> | ||
<input type="hidden" name="form-config" value='{{form_config | tojson }}' /> | ||
<input id="record-permissions" type="hidden" name="record-permissions" value='{{permissions | tojson }}' /> | ||
<input id="links" type="hidden" name="links" value='{{links | tojson }}' /> | ||
<div id="form-app"></div> | ||
{%- endblock form_main_content -%} | ||
{% endblock page_body %} | ||
``` | ||
|
||
As you can see here, this template cannot be used on its own. You will need to extend this template in your ui app and | ||
inject atleast some `your-formapp-entrypoint-here.js` JS entrypoint handling your React Form app to the `javascript` block. | ||
|
||
### React hooks & utils | ||
|
||
This library provides utilities and React hooks to help you with creating your own React form application. | ||
|
||
#### createFormAppInit | ||
|
||
A form application initialization function, that: | ||
|
||
- Reads & parses hidden inputs from the Jinja template. | ||
- Loads any overridden React components passed as `defaultComponents` (see [react-overridable](https://github.com/indico/react-overridable)). | ||
- Creates a React Context Provider `FormConfigProvider` with config values from hidden inputs. | ||
- Finds an element with `id=form-app` in the Jinja template. | ||
- Renders a root Form app layout component given by overridable id `FormApp.layout`, wrapped in `FormConfigProvider` and `ContainerComponent`. | ||
|
||
```jsx | ||
createFormAppInit( | ||
defaultComponents, | ||
autoInit = true, | ||
ContainerComponent = React.Fragment | ||
) | ||
``` | ||
|
||
#### useFormConfig | ||
|
||
A React hook used to access `FormConfigProvider` context in `FormApp.layout` and any of its children components. | ||
|
||
```jsx | ||
const { record, formConfig, recordPermissions, links } = useFormConfig(); | ||
``` | ||
|
||
#### useOnSubmit | ||
|
||
Used for handling Formik form submission. | ||
|
||
```jsx | ||
export const useOnSubmit = ({ | ||
apiUrl, // Target URL for apiClient to make requests to | ||
context = submitContextType.create, // Submission context, e.g. "create", "update"... | ||
apiClient = OARepoDepositApiClient, // API client implementation instance | ||
onBeforeSubmit = () => { }, // Callback (or array of) functions, called before submit request | ||
onSubmitSuccess = () => { }, // Callback (or array of) functions, called on successful submit request | ||
onSubmitError = () => { } // Callback (or array of) functions, called when submit request failed | ||
}) => { onSubmit, submitError } | ||
``` |
Oops, something went wrong.