Skip to content

Commit

Permalink
Merge pull request #2 from Narodni-repozitar/mirekys/fe-11-create-dev…
Browse files Browse the repository at this point in the history
…eloper-docs-for-deposits

[mirekys/FE-11] documentation on forms usage & customization
  • Loading branch information
mirekys authored Aug 4, 2023
2 parents ac10b2d + c7e38e9 commit a903f38
Show file tree
Hide file tree
Showing 12 changed files with 8,177 additions and 8,831 deletions.
1 change: 1 addition & 0 deletions .node-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
16
6 changes: 5 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
{}
{
"workbench.colorCustomizations": {
"editorInfo.foreground": "#0080ff6a"
}
}
71 changes: 71 additions & 0 deletions docs/technology/invenio/customize-ui/forms/form-config.md
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
):
```
5 changes: 5 additions & 0 deletions docs/technology/invenio/customize-ui/forms/index.md
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.
202 changes: 202 additions & 0 deletions docs/technology/invenio/customize-ui/forms/overview.md
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 }
```
Loading

0 comments on commit a903f38

Please sign in to comment.