Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 167 additions & 64 deletions docs/operate/customize/look-and-feel/override_components.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

*Introduced in InvenioRDM v11*

This documentation is targeted to developers who want to customize specific UI React components in an instance.
This documentation is targeted at developers who want to customize specific UI React components in an instance.
For this guide, we assume that you are familiar with React and JavaScript.

## Override React components
## About overriding

!!! warning "Experimental feature"

Expand All @@ -18,39 +18,34 @@ For this guide, we assume that you are familiar with React and JavaScript.
The UI of InvenioRDM is composed of classic HTML web pages for mostly static content, and React web apps for very dynamic content to enhance the user experience.
While a [dedicated guide](./templates.md) describes how to override HTML web pages (Jinja templates), this guide focus on how to override React components.

InvenioRDM uses the React library [`react-overridable`](https://github.com/indico/react-overridable). The library provides a mechanism to mark React components as "overridable" by id.
Developers can define a map `{ id: Overridden React component }`, which is then applied when each React component is rendered: the overridden component is rendered instead of the default one.
InvenioRDM uses the React library [`react-overridable`](https://github.com/indico/react-overridable).
The library provides a mechanism to mark React components as "overridable" by ID, which is implemented for many components across the InvenioRDM codebase.
Developers can define a mapping which is then applied when each React component is rendered.

As example for this guide, you will learn how to override the UI React component **in the upload form that marks a record as "Metadata only"**. More specifically, you will replace the "Metadata-only record" checkbox with a toggle, a "switch-like" component.
## 1. Find the component to override

![Metadata-only record checkbox](./imgs/metadata_only_checkbox.png)

## Steps

### Identify how to override

At the moment, the easiest way to understand how to identify if the component that you want to override is a classic HTML component or a React component is to use the Developer Tools in your browser (e.g. [Chrome](https://developer.chrome.com/docs/devtools/) or [Firefox DevTools](https://firefox-source-docs.mozilla.org/devtools-user/)). You can inspect the code and take advantage of some useful [React browser extensions](https://beta.reactjs.org/learn/react-developer-tools) to select and inspect elements:
At the moment, the easiest way to identify if the component that you want to override is a classic HTML component or a React component is to use the Developer Tools in your browser (e.g. [Chrome](https://developer.chrome.com/docs/devtools/) or [Firefox DevTools](https://firefox-source-docs.mozilla.org/devtools-user/)). You can inspect the code and take advantage of the [React Developer Tools](https://react.dev/learn/react-developer-tools) browser extension to select and inspect elements:

![React browser extension example](./imgs/react_browser_extension_example.png)

You can then find the component in the InvenioRDM modules source code, searching it in your local development environment or using the search feature in GitHub in the [inveniosoftware organization](https://github.com/search?q=org%3Ainveniosoftware+FileUploaderToolbar&type=code).

You can always [ask for help](../../../install/troubleshoot.md#getting-help)!
If the component shows up in the React tree, it is a React component and can be overriden using the methods described on this page.
Otherwise, it is an HTML component that can be [overriden using Jinja templates](./templates.md).

### Find the React Overridable ID

The easiest way to to find the ID of an overridable component is to use `react-overridable`'s built-in developer tool.
Next, you can find the ID of an overridable component using `react-overridable`'s built-in developer tool.
Simply open a browser console on your local instance and call the global function `reactOverridableEnableDevMode()`.
All overridable components will display a small red overlay tag showing their ID.
You can click a tag to copy its ID to your clipboard.

![Metadata-only checkbox overridable ID in an overlay](./imgs/metadata_id_overlay.png)

The React component's overridable ID for the '`Metadata-only record`' checkbox component is `ReactInvenioDeposit.FileUploaderToolbar.MetadataOnlyToggle.container`. It can be found in the [`invenio-rdm-records`](https://github.com/inveniosoftware/invenio-rdm-records/blob/dd72962b713f07b81699f7d5c9a8a673d585466a/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/fields/FileUploader/FileUploaderToolbar.js#L56) module.
You can search the ID in the [`inveniosoftware` organisation on GitHub](https://github.com/inveniosoftware/) to find the component and its props, which
can be helpful when overriding.

If you're struggling, you can always [ask for help](../../../install/troubleshoot.md#getting-help)!

### The mapping file
## 2. Find or create the mapping file

In a new InvenioRDM v11 or above installations, an almost empty file named `mapping.js` is available at the following path in your `assets` folder:
In InvenioRDM v11 or above installations, a file named `mapping.js` is available at the following path in your `assets` folder:

```terminal
├── assets
Expand All @@ -60,47 +55,114 @@ In a new InvenioRDM v11 or above installations, an almost empty file named `mapp
| | | | ├── mapping.js
```

For existing installations, you will have to create it. It is a very simple file:
For installations prior to v11, you will have to create it. It is a very simple file:

```javascript
export const overriddenComponents = {};
```

The `const overriddenComponents` is the map that will contain all your future overridden components.
The `const overriddenComponents` is the map that will contain all your future overrides.

## 3. Override the component

### New component creation
The override can be specified in one of three ways, depending on your use case and the amount of control you require:

Let's create a new React component, very similar to the default `FileUploaderToolbar`, changing the UI component that will render. In the same file `mapping.js`, add the following code above the `const overriddenComponents`:
- a static override of the props passed to the component
- a 'dynamic' override of the props based on the form's state
- completely replacing a component with a custom one

### Statically override a component's props

You can use the `parametrize` function built into `react-overridable`, into which you need to pass the component you wish to override and an object containing your props.
These props will be 'merged' with the existing props, with yours taking precedence over existing ones of the same name.

In this case, the props are defined once in your `mapping.js` file and are not updated during the runtime of the application.
You are also unable to access any React/Formik context while defining the props.

```javascript
import React from "react";
import { Checkbox } from "semantic-ui-react";
import { useFormikContext } from "formik";
import PropTypes from "prop-types";

const MetadataToggle = (props) => {
const { filesEnabled } = props;
const { setFieldValue } = useFormikContext();

const handleOnChangeMetadataOnly = () => {
setFieldValue("files.enabled", !filesEnabled);
setFieldValue("access.files", "public");
};

return (
<Checkbox
toggle
label="Metadata-only record"
onChange={handleOnChangeMetadataOnly}
/>
);
};
import { parametrize } from "react-overridable"
import { TitlesField } from "@js/invenio_rdm_records"

export const overriddenComponents = {
"InvenioRdmRecords.DepositForm.TitlesField": parametrize(
TitlesField,
{
helpText: "Describe your resource in a few words"
}
)
}
```

### Dynamically override a component's props

To implement more complex functionality in the deposit form, you can override the props of components by using a custom function.
This allows you to express a range of behaviours:

- hiding fields that are inapplicable to a certain resource type
- changing the label of fields to be more contextually relevant
- marking a field as disabled when its value has been made obvious by the value of another field
- showing certain fields only when a specific community is selected
- etc.

For this, you can use the `dynamicParametrize` function in `react-invenio-forms`, which behaves similarly to `parametrize`.
The callback you pass will be evaluated whenever the form state changes, and the object you return will override the props
passed to the component.

```javascript
import { dynamicParametrize } from "react-invenio-forms"
import { RelatedWorksField } from "@js/invenio_rdm_records"

export const overriddenComponents = {
"InvenioRdmRecords.DepositForm.TitlesField": dynamicParametrize(
TitlesField,
({ formValues }) => {
return {
helpText: `Enter the title of your ${formValue.metadata.resource_type}`
}
}
)
}
```

The callback function is currently passed an object as its single argument, containing the following values:

- `formValues`: the raw values of the entire deposit form as given by Formik. The majority of field values are under the `metadata` key.
- `existingProps`: the props passed to the element before your override.

export default MetadataToggle;
### Fully replace a component

MetadataToggle.propTypes = {
filesEnabled: PropTypes.bool.isRequired,
To fully replace a component with your custom one, first create the component definition within your instance's source code.
For example:

```jsx
import React from "react";
import { Checkbox } from "semantic-ui-react";
import { useFormikContext } from "formik";
import PropTypes from "prop-types";

const MetadataToggle = (props) => {
const { filesEnabled } = props;
const { setFieldValue } = useFormikContext();

const handleOnChangeMetadataOnly = () => {
setFieldValue("files.enabled", !filesEnabled);
setFieldValue("access.files", "public");
};

return (
<Checkbox
toggle
label="Metadata-only record"
onChange={handleOnChangeMetadataOnly}
/>
);
};

export default MetadataToggle;

MetadataToggle.propTypes = {
filesEnabled: PropTypes.bool.isRequired,
};
```

Now, change the map by adding your new component:
Expand All @@ -109,34 +171,75 @@ Now, change the map by adding your new component:
...

export const overriddenComponents = {
"ReactInvenioDeposit.FileUploaderToolbar.MetadataOnlyToggle.container": MetadataToggle,
"InvenioRdmRecords.DepositForm.FileUploaderToolbar.MetadataOnlyToggle": MetadataToggle,
};
```

Lastly, rebuild your assets and run the instance:

```terminal
cd my-site
invenio-cli assets build
invenio-cli run
```
## Common field props in the deposit form

The built-in fields in the deposit form (e.g. title, description, etc.) have a number of common props to make customizing basic functionality easier.

The following props may be overriden on all built-in fields:

When navigating to the upload form, you should now see your new React component instead of the default:
- `label`: the user-facing short label
- `labelIcon`: the ID of the [Semantic UI Icon](https://semantic-ui.com/elements/icon.html) to include in the label
- `helpText`: a small optional text generally shown below the field, providing additional context to the user
- `placeholder`: same as the [HTML attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/placeholder)

!["`Metadata-only record`" toggle](./imgs/metadata_only_toggle.png)
Additionally, the following props may be overriden on fields that are not mandatory.
At the moment, this is all fields except Resource Type, Title, Publication Date, and Creatibutors.

## Other examples
- `hidden`: if `true`, the field is not rendered at all
- If a field already had a value before being hidden, this will still be included in the model and will be sent to the server when the form is submitted.
- Fields retain their values when they are hidden and later re-shown.
- `disabled`: same as the [HTML attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/disabled)
- `required`: if `true`, shows a red asterisk in the field label
- This does not affect the form validation model on the frontend or the backend, and is purely a stylistic setting.

Let's hide completely the '`Metadata-only record`'checkbox from the upload form.
Many fields have their own props in addition to these.
Please [view the source code](https://github.com/inveniosoftware/invenio-rdm-records/tree/master/invenio_rdm_records/assets/semantic-ui/js/invenio_rdm_records/src/deposit/fields) for more details.

It is possible to remove a component using the overridable strategy. In the previous example, instead of declaring a target component `MetadataToggle` you can simply change the map to:
## Examples

### Showing/hiding

You can completely hide a field in the deposit form, based on either a static `true`/`false` value or in response to the current state of the form.

To simply permanently hide the field, you can use something like this:

```javascript
export const overriddenComponents = {
"ReactInvenioDeposit.FileUploaderToolbar.MetadataOnlyToggle.container": () => null,
"InvenioRdmRecords.DepositForm.RelatedWorksField": () => null,
};
```

The expression `() => null` above is defining an "empty" component, thus removing it from the upload form.
The expression `() => null` above is defining an "empty" component, thus removing it from the deposit form.

To dynamically only show the field when the `image` resource type is selected, you can use the `dynamicParametrize` function.

```javascript
import { dynamicParametrize } from "react-invenio-forms"
import { RelatedWorksField } from "@js/invenio_rdm_records"

export const overriddenComponents = {
"InvenioRdmRecords.DepositForm.RelatedWorksField": dynamicParametrize(
TitlesField,
({ formValues }) => {
return {
hidden: formValues.metadata.resource_type !== "image"
}
}
)
}
```

In order to see more examples in action, you can check the [zenodo-rdm](https://github.com/zenodo/zenodo-rdm) repository!

### Custom form fields

You can also override your custom deposit form fields if they use the built-in UI widgets.
This can be useful if you want to dynamically set the props of the widgets without fully re-implementing them yourself.
In this case, the ID of the overridable is the same as the field name (e.g. `cern:experiments`).

For more details, see [the UI widgets documentation](../metadata/custom_fields/widgets.md#dynamic-behaviour).
30 changes: 30 additions & 0 deletions docs/operate/customize/metadata/custom_fields/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,36 @@ These props are applicable to all widgets. In the reference below, only props ad

To improve consistency with the fields in the deposit form, the `icon` prop has been renamed to `labelIcon` and `description` has been renamed to `helpText`. The functionality of these props is unchanged and the old names will continue working for now, albeit with a deprecation notice.

## Dynamic behaviour

While controlling the static prop values via the [`RDM_CUSTOM_FIELDS_UI` config value](records.md#upload-deposit-form) is relatively straightforward, it doesn't allow
you to specify dynamic behaviour, such as showing/hiding the field only in specific cases, or using a different `helpText` depending on the resource type.

This can instead be done using the `dynamicParametrize` function.
For more details on its usage, [see the documentation on overriding React components](../../look-and-feel/override_components.md#dynamic).

The ID of the overridable is the internal name of your custom field (e.g. `cern:experiment`).
You can override any of the props listed on this page (except `fieldPath`), depending on the specific widget.

For example, to make the `cern:experiment` field only be shown for thesis records:

```javascript
// my-rdm-instance/assets/js/invenio_app_rdm/overridableRegistry/mapping.js

import { dynamicParametrize, Input } from "react-invenio-forms"

export const overriddenComponents = {
"cern:experiment": dynamicParametrize(
Input,
({ formValues }) => {
return {
hidden: formValues.metadata.resource_type !== "thesis"
}
}
)
}
```

## Input

An input field for a single string value.
Expand Down
12 changes: 12 additions & 0 deletions docs/releases/vNext/upgrade-vNext.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,22 @@ affected by `invenio index destroy --yes-i-know` and are totally functional afte

### Required changes

#### Overridable IDs in the deposit form

To improve consistency in naming conventions and structure, some IDs of Overridables in the deposit form have been modified. If you are overriding any of these components, you will need to change the ID in your mapping file to reflect these modifications.

The full list of ID changes [can be found here](https://github.com/inveniosoftware/invenio-rdm-records/pull/2101/files#diff-ff3c479edefad986d2fe6fe7ead575a46b086e3bbcf0ccc86d85efc4a4c63c79).

If you are not overriding any of these components, you do not need to change anything.

### Optional changes

#### Deprecations

##### Custom field widget prop names

Many [custom field widgets](../../operate/customize/metadata/custom_fields/widgets.md) used the `icon` and `description` props, which have now been deprecated and replaced with `labelIcon` and `helpText` respectively. This is to improve consistency with the naming of the built-in fields used in the deposit form and thereby avoid confusion. The old names will continue to function for now, but we recommend updating to the new names where applicable.

#### New configuration variables

These are the new configuration variables introduced in this release. Make sure that you read the related documentation before enabling them. Add them to your `invenio.cfg` as needed:
Expand Down
3 changes: 2 additions & 1 deletion docs/releases/vNext/version-vNext.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,11 @@ Here is a quick summary of the myriad of other improvements in this release:

## Deprecations


- Many [custom field widgets](../../operate/customize/metadata/custom_fields/widgets.md) used the `icon` and `description` props, which have now been deprecated and replaced with `labelIcon` and `helpText` respectively. This is to improve consistency with the naming of the built-in fields used in the deposit form and thereby avoid confusion. The old names will continue to function for now.

## Breaking changes

- Overridables in the deposit form have been modified to improve consistency in structure and naming conventions. This has involved renaming the IDs of several `<Overridable>`s, but none have been removed. If you are using these IDs to override components, please see [the full list of updates](https://github.com/inveniosoftware/invenio-rdm-records/pull/2101/files#diff-ff3c479edefad986d2fe6fe7ead575a46b086e3bbcf0ccc86d85efc4a4c63c79) and change your IDs accordingly.

## Requirements

Expand Down