Skip to Content
CustomizeModel UIQuick start tutorial

Quick Start: Customizing Your Model UI

After generating your repository with nrp-model-copier, you’ll want to customize the starter pages to display your actual record model. This tutorial walks you through the most common customization tasks.

This tutorial assumes you’ve already generated a model using nrp-model-copier and have a working repository. For detailed architecture and configuration topics, see the in-depth documentation for Record landing page, Deposit form, and Search page.

Overview

The nrp-model-copier template provides starter files that you edit directly to customize your model’s UI:

ComponentPurposeStarter FileDocumentation
Record detailDisplays individual recordui/{{model_name}}/templates/semantic-ui/{{model_name}}/record_detail/main.htmlRecord landing page
Deposit formForm for creating/editing recordsui/{{model_name}}/semantic-ui/js/{{model_name}}/forms/FormFieldsContainer.jsxDeposit form
Search resultHow records appear in searchui/{{model_name}}/semantic-ui/js/{{model_name}}/search/ResultsListItem.jsxSearch page

For JavaScript file changes (deposit form, search result), run the assets watcher in a separate terminal to automatically rebuild on file changes:

./run.sh cli assets watch

For more information, see Run frontend assets builder.


1. Customize Record Detail Page

The record detail page starter template contains predefined JinjaX blocks that you modify to display your metadata.

Step 1: Find the metadata field you want to display

Looking at your metadata schema:

{{model_name}}/metadata.yaml
# Definition of metadata for {{model_name}} Metadata: properties: title: type: fulltext+keyword label: en: Title description: type: fulltext label: en: Description authors: type: array items: type: keyword label: en: Authors

Step 2: Edit the starter template

Edit ui/{{model_name}}/templates/semantic-ui/{{model_name}}/record_detail/main.html:

ui/{{model_name}}/templates/semantic-ui/{{model_name}}/record_detail/main.html
{% extends "oarepo_ui/record_detail/main.html" %} {%- block record_content -%} {%- if record.metadata.description -%} <div class="ui segment"> <p>{{ record.metadata.description }}</p> </div> {%- endif -%} {%- if record.metadata.authors -%} <div class="ui segment"> <strong>Authors:</strong> {{ record.metadata.authors | join(', ') }} </div> {%- endif -%} {%- endblock record_content -%}

The starter template includes commented-out blocks - uncomment the blocks you want to customize.

For available override blocks, see also Template structure.

Step 3: Visit the page

Visit a record detail page at https://127.0.0.1:5000/mymodel/your-record-id

2. Customize Deposit Form

Modify the deposit form starter component to match your record’s metadata schema.

Step 1: Edit the starter form component

Edit ui/{{model_name}}/semantic-ui/js/{{model_name}}/forms/FormFieldsContainer.jsx and add form fields that match your model’s metadata schema:

ui/{{model_name}}/semantic-ui/js/{{model_name}}/forms/FormFieldsContainer.jsx
import React from "react"; import { Accordion, Form } from "semantic-ui-react"; import { TextField, RichInputField, ArrayField } from "@js/oarepo_ui/forms"; export const FormFieldsContainer = (props) => { return ( <Accordion fluid className="pr-1 ml-0"> <Accordion.Content id="general" activeTitle="General"> <Form.Field required> <label>Title</label> <TextField fieldPath="metadata.title" label="Title" required {...props} /> </Form.Field> <Form.Field> <label>Description</label> <RichInputField fieldPath="metadata.description" label="Description" {...props} /> </Form.Field> <Form.Field> <label>Authors</label> <ArrayField fieldPath="metadata.authors" label="Authors" {...props} > <ArrayInput.Item> <TextField fieldPath="" label="Author" required /> </ArrayInput.Item> </ArrayField> </Form.Field> </Accordion.Content> </Accordion> ); }; export default FormFieldsContainer;

For more on deposit form structure, see Building Your Deposit Form.

Step 2: View changes

The assets watcher will automatically rebuild your changes. Visit the deposit page to see the updated form.

Step 3: Test the form

Visit the deposit page at https://127.0.0.1:5000/mymodel/uploads/new

Available Form Fields

ComponentBest ForDocumentation
TextFieldSimple text inputTextField
RichInputFieldRich text/descriptionRichInputField
ArrayFieldRepeating fieldsArrayField
SelectFieldDropdown selectionSelectField
DateFieldDate/time pickersDateField
BooleanFieldYes/no togglesBooleanField

For a complete list, see Deposit form components.

3. Customize Search Result Item

Edit the search result starter component to display how your records appear in search.

Step 1: Edit the starter component

Edit ui/{{model_name}}/semantic-ui/js/{{model_name}}/search/ResultsListItem.jsx:

ui/{{model_name}}/semantic-ui/js/{{model_name}}/search/ResultsListItem.jsx
import React from "react"; import PropTypes from "prop-types"; import _get from "lodash/get"; import { Item } from "semantic-ui-react"; import { i18next } from "@translations/i18next"; export const ResultsListItem = ({ result }) => { const title = _get(result, "metadata.title", i18next.t("No title")); const description = _get(result, "metadata.description", ""); const authors = _get(result, "metadata.authors", []); return ( <Item key={result.id}> <Item.Content> <Item.Header as="h2"> <a href={result.links.self_html}>{title}</a> </Item.Header> {description && ( <Item.Description> {description} </Item.Description> )} {authors.length > 0 && ( <Item.Meta> Authors: {authors.join(", ")} </Item.Meta> )} </Item.Content> </Item> ); }; ResultsListItem.propTypes = { result: PropTypes.object.isRequired, }; export default ResultsListItem;

For more on building your search result item, see Building Your Search Result Item.

Step 2: View search results

Visit the search page at https://127.0.0.1:5000/mymodel


Putting It All Together

Here’s a complete example for a “Book” record model:

Metadata Schema

{{model_name}}/metadata.yaml
# Definition of metadata for {{model_name}} Metadata: properties: title: type: fulltext+keyword label: en: Title subtitle: type: fulltext label: en: Subtitle isbn: type: keyword label: en: ISBN authors: type: array items: type: keyword label: en: Authors publisher: type: keyword label: en: Publisher published_date: type: datetime label: en: Published date abstract: type: fulltext label: en: Abstract

For more on defining metadata schema, see Metadata.

Record Detail Template

ui/{{model_name}}/templates/semantic-ui/{{model_name}}/record_detail/main.html
{% extends "oarepo_ui/record_detail/main.html" %} {%- block record_content -%} <div class="ui segment"> <div class="ui divided items"> {%- if record.metadata.authors -%} <div class="item"> <div class="content"> <strong>Authors:</strong> <ul> {%- for author in record.metadata.authors -%} <li>{{ author }}</li> {%- endfor -%} </ul> </div> </div> {%- endif -%} {%- if record.metadata.publisher -%} <div class="item"> <div class="content"> <strong>Publisher:</strong> {{ record.metadata.publisher }} </div> </div> {%- endif -%} {%- if record.metadata.published_date -%} <div class="item"> <div class="content"> <strong>Published:</strong> {{ record.metadata.published_date }} </div> </div> {%- endif -%} {%- if record.metadata.isbn -%} <div class="item"> <div class="content"> <strong>ISBN:</strong> {{ record.metadata.isbn }} </div> </div> {%- endif -%} </div> </div> {%- if record.metadata.abstract -%} <div class="ui segment"> <strong>Abstract:</strong> <p>{{ record.metadata.abstract }}</p> </div> {%- endif -%} {%- endblock record_content -%}

For more detail page customization options, see Record landing page.

Deposit Form

ui/{{model_name}}/semantic-ui/js/{{model_name}}/forms/FormFieldsContainer.jsx
import React from "react"; import { Accordion, Form } from "semantic-ui-react"; import { TextField, RichInputField, DateField, ArrayField } from "@js/oarepo_ui/forms"; export const FormFieldsContainer = (props) => { return ( <Accordion fluid className="pr-1 ml-0"> <Accordion.Content id="general" activeTitle="General"> <Form.Field required> <TextField fieldPath="metadata.title" label="Title" required {...props} /> </Form.Field> <Form.Field> <TextField fieldPath="metadata.subtitle" label="Subtitle" {...props} /> </Form.Field> <Form.Field> <TextField fieldPath="metadata.isbn" label="ISBN" {...props} /> </Form.Field> <Form.Field> <TextField fieldPath="metadata.publisher" label="Publisher" {...props} /> </Form.Field> <Form.Field> <DateField fieldPath="metadata.published_date" label="Published date" {...props} /> </Form.Field> </Accordion.Content> <Accordion.Content id="authors"> <Form.Field> <ArrayField fieldPath="metadata.authors" label="Authors" {...props} > <ArrayInput.Item> <TextField fieldPath="" label="Author" required /> </ArrayInput.Item> </ArrayField> </Form.Field> </Accordion.Content> <Accordion.Content id="abstract"> <Form.Field> <RichInputField fieldPath="metadata.abstract" label="Abstract" {...props} /> </Form.Field> </Accordion.Content> </Accordion> ); }; export default FormFieldsContainer;

Search Result Item

ui/{{model_name}}/semantic-ui/js/{{model_name}}/search/ResultsListItem.jsx
import React from "react"; import PropTypes from "prop-types"; import _get from "lodash/get"; import { Item, Label } from "semantic-ui-react"; import { i18next } from "@translations/i18next"; export const ResultsListItem = ({ result }) => { const title = _get(result, "metadata.title", i18next.t("No title")); const subtitle = _get(result, "metadata.subtitle", ""); const authors = _get(result, "metadata.authors", []); const isbn = _get(result, "metadata.isbn", ""); return ( <Item key={result.id}> <Item.Content> <Item.Header as="h2"> <a href={result.links.self_html}>{title}</a> </Item.Header> {subtitle && ( <Item.Meta>{subtitle}</Item.Meta> )} {authors.length > 0 && ( <Item.Description> By {authors.join(", ")} </Item.Description> )} {isbn && ( <Item.Extra> <Label>ISBN: {isbn}</Label> </Item.Extra> )} </Item.Content> </Item> ); }; ResultsListItem.propTypes = { result: PropTypes.object.isRequired, }; export default ResultsListItem;

For more search result customization and configuration, see Search page.

Further Reading

Last updated on