Rendering basics
Contents
In this chapter you will add a new component that renders persons
Person content type
The Headless Movie DB contains sample content, and content types. One of these is Person
. From Content Studio, you may find and list persons. By selecting the JSON preview, you can also study the properties of a person:
{
"_id": "a8b374a2-c532-45eb-9aa1-73d1c37cd681",
"_name": "lea-seydoux",
"_path": "/hmdb/persons/lea-seydoux",
"creator": "user:system:su",
"modifier": "user:system:su",
"createdTime": "2021-11-23T10:01:16.711Z",
"modifiedTime": "2021-11-23T13:06:38.493Z",
"owner": "user:system:su",
"type": "com.enonic.app.hmdb:person",
"displayName": "Léa Seydoux",
"hasChildren": true,
"language": "en",
"valid": true,
"childOrder": "modifiedtime DESC",
"data": {
"photos": "09b3af0e-6da3-4bcf-88d9-11cbe9c41283",
"bio": "French actress Léa Seydoux was born in 1985 in Paris, France, to Valérie Schlumberger, a philanthropist, and Henri Seydoux, a businessman.",
"dateofbirth": "1985-07-01"
},
"x": {},
"page": {},
"attachments": {},
"publish": {
"from": "2024-11-13T13:32:39.427Z",
"first": "2024-11-13T13:32:39.427Z"
},
"workflow": {
"state": "READY",
"checks": {}
}
}
To implement the rendering we need to add code that will perform a two-step operation:
Fetch the data
-
Create a data fetcher
/src/main/resources/react4xp/components/content/PersonProcessor.tsimport {get as getContentByKey} from '/lib/xp/content'; import {imageUrl} from '/lib/xp/portal'; import {toArray} from "/react4xp/utils/arrayUtils"; import {parentPath} from '/react4xp/utils/path'; import {PageDescriptor} from '@enonic-types/core'; import type {Content} from '@enonic-types/lib-content'; import type {ComponentProcessorFunction} from '@enonic-types/lib-react4xp/DataFetcher'; function fetchAdditionalPhotos(photosIds) { return photosIds.map(photoId => { const photoContent = getContentByKey<Content>({key: photoId}); return { _id: photoContent._id, title: photoContent.displayName, imageUrl: imageUrl({id: photoContent._id, scale: 'block(175, 175)'}) }; }); } export const personProcessor: ComponentProcessorFunction<PageDescriptor> = (params) => { const photos: string[] = toArray<string>(params.content.data.photos as string | string[]) const firstPhotoId = photos[0] || ''; const remainingPhotoIds = photos.slice(1) || ''; const {_id, displayName} = getContentByKey<Content>({key: firstPhotoId}); const extraPhotos = fetchAdditionalPhotos(remainingPhotoIds); return { displayName: `${params.content.displayName}`, photo: { _id, title: displayName, imageUrl: imageUrl({id: _id, scale: 'block(1200, 675)'}) }, birthDate: params.content.data.dateofbirth, restPhotos: extraPhotos, parent: parentPath(params.request.path) }; };
-
Then add it to the dataFetcher, with these lines:
dataFetcherimport {personProcessor} from './components/content/PersonProcessor'; dataFetcher.addContentType('com.enonic.app.hmdb:person', {processor: personProcessor});
We are now able to fetch the Person data, next up we need to render it.
Register component
The component consists of two files:
import React from 'react'
import styles from './Person.module.css';
export const Person = (props) => {
const {parent, displayName, photo, restPhotos, bioHtml, birthDate} = props as any;
return (
<div className={styles.person}>
<div className={"back"}>
<a href={parent}>
<p>Back</p>
</a>
</div>
<h1>{displayName}</h1>
<p>{birthDate}</p>
<div>
{
photo ? (
<>
<div className={styles.photos}>
<img src={photo.imageUrl}
title={photo.title}
alt={photo.title}
height={675}
width={1200}
loading="eager"
/>
</div>
</>
) : (
<p>No photo available</p>
)
}
{restPhotos && restPhotos.length > 0 && (
<>
<h2>Photos</h2>
<div className={styles.photoContainer}>
<div className={styles.photoGrid}>
<div className={styles.photoScroll}>
{restPhotos.map((photo, index) => (
<img key={index}
src={photo.imageUrl}
title={photo.title}
alt={photo.title}
height={175}
width={175}
/>
))}
</div>
</div>
</div>
</>
)}
</div>
</div>
)
}
+ ./src/main/resources/react4xp/components/content/Person.module.css (CSS module)
.person .bio {
font-family: sans-serif;
}
.person {
display: flex;
flex-direction: column;
margin-inline: auto;
min-height: 1600px;
}
.person h1 {
margin-bottom: 0;
}
.person p {
margin-top: 0;
}
.flexBlock {
display: flex;
justify-content: space-between;
}
.photoContainer {
position: relative;
display: flex;
::-webkit-scrollbar {
-webkit-appearance: none;
height: 6px;
}
::-webkit-scrollbar-thumb {
border-radius: 8px;
background-color: ghostwhite;
}
}
.photoGrid {
overflow-x: auto;
}
.photoScroll {
display: flex;
gap: 25px;
white-space: nowrap;
padding: 0;
margin: .5rem 0 .5rem 0;
}
.photoScroll img {
border-radius: 5px;
}
.photos {
margin-block: 1rem;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
grid-gap: 50px;
gap: 50px;
}
.restPhotosContainer {
padding: 1rem 0;
max-width: calc(90vw - 1rem);
}
.restPhotos {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15%, 1fr));
gap: 5%;
margin-block: 1rem;
}
.photos img {
width: 100%;
height: auto;
border-radius: 8px;
}
.restImg {
width: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
object-position: 50% 50%;
border-radius: 5px;
}
.richText {
font-size: 24px;
}
.richText figure img {
border-radius: 5px;
}
.photosHeader {
margin-top: 0;
color: #EF82F0;
}
Once they are created register the component in the componentRegistry, by adding the following lines:
import {Person} from './components/content/Person';
componentRegistry.addContentType('com.enonic.app.hmdb:person', {View: Person});
Result
Back in Content Studio, select a random person to see the glorious result.
Next
You are on a roll, next - lets look into app layout.