Content management systems can output rich text data in a number of formats. Many CMS systems output in JSON format that can embed objects. I am looking for a method to support rendering dynamic elements with dynamic attributes and potentially rendering astro components where objects are embedded.
Astro JS has a means of rendering html using set:html directive, however, this requires rendering the json block as html including embedded objects. Notice the following JSON which is output from Umbraco Headless (this same concept applies to multiple CMS's).
"blogRichText": {
"tag": "#root",
"attributes": {},
"elements": [
{
"tag": "p",
"attributes": {
"id": "someId",
"class": "some-class"
},
"elements": [
{
"text": "This is ",
"tag": "#text"
},
{
"tag": "strong",
"attributes": {},
"elements": [
{
"text": "bold text",
"tag": "#text"
}
]
},
{
"text": " with an object folowing",
"tag": "#text"
}
]
},
{
"tag": "umb-rte-block",
"attributes": {
"content-id": "705a6633-3e20-4f3a-be76-4b450a397608"
},
"elements": []
},
{
"tag": "p",
"attributes": {},
"elements": [
{
"text": "with some more text",
"tag": "#text"
}
]
}
],
"blocks": [
{
"content": {
"contentType": "angularCounter",
"id": "705a6633-3e20-4f3a-be76-4b450a397608",
"properties": {}
},
"settings": null
}
]
}
So I am looking for a means of creating a dynamic component that could contain dynamic components, something like the following:
---
const dynamicElement = {
"tag": "p",
"attributes": {},
"elements": [
{
"text": "with some more text",
"tag": "#text"
}
]
};
---
<{dynamicElement.tag}
{dynamicElement.attributes.length > 0 && (
{dynamicElement.attributes.map((attr) => (
{attr.name}={attr.value}
))}
)}>
{dynamicElement.elements.length > 0 && (
{dynamicElement.elements.map((elem) => (
<dynamicElement element={elem}/>
))}
)}
</{dynamicElement.tag}>
Is this possible?
Content management systems can output rich text data in a number of formats. Many CMS systems output in JSON format that can embed objects. I am looking for a method to support rendering dynamic elements with dynamic attributes and potentially rendering astro components where objects are embedded.
Astro JS has a means of rendering html using set:html directive, however, this requires rendering the json block as html including embedded objects. Notice the following JSON which is output from Umbraco Headless (this same concept applies to multiple CMS's).
"blogRichText": {
"tag": "#root",
"attributes": {},
"elements": [
{
"tag": "p",
"attributes": {
"id": "someId",
"class": "some-class"
},
"elements": [
{
"text": "This is ",
"tag": "#text"
},
{
"tag": "strong",
"attributes": {},
"elements": [
{
"text": "bold text",
"tag": "#text"
}
]
},
{
"text": " with an object folowing",
"tag": "#text"
}
]
},
{
"tag": "umb-rte-block",
"attributes": {
"content-id": "705a6633-3e20-4f3a-be76-4b450a397608"
},
"elements": []
},
{
"tag": "p",
"attributes": {},
"elements": [
{
"text": "with some more text",
"tag": "#text"
}
]
}
],
"blocks": [
{
"content": {
"contentType": "angularCounter",
"id": "705a6633-3e20-4f3a-be76-4b450a397608",
"properties": {}
},
"settings": null
}
]
}
So I am looking for a means of creating a dynamic component that could contain dynamic components, something like the following:
---
const dynamicElement = {
"tag": "p",
"attributes": {},
"elements": [
{
"text": "with some more text",
"tag": "#text"
}
]
};
---
<{dynamicElement.tag}
{dynamicElement.attributes.length > 0 && (
{dynamicElement.attributes.map((attr) => (
{attr.name}={attr.value}
))}
)}>
{dynamicElement.elements.length > 0 && (
{dynamicElement.elements.map((elem) => (
<dynamicElement element={elem}/>
))}
)}
</{dynamicElement.tag}>
Is this possible?
Share Improve this question asked Feb 5 at 8:28 J FoJ Fo 1 3 |1 Answer
Reset to default 0I figured this out. If you are using the Umbraco Delivery API, you should pretty much be able to use this out of the box just providing your own RichTextFieldBlockItem component if you are embedding block components in your rich text. For others using a similar JSON rich text or other hierarchical json structure, this might be a helpful pattern.
There are two key elements in the following block of code:
- The import RenderRichTextComponent is a reference to itself. Thus it becomes a recursive component.
- const GenericTag = genericNode?.tag || 'div' allows for creating custom tags. Note the GenericTag variable name must be capitalized otherwise astro would just write out the variable name instead of the variable value.
code:
---
// filepath: /src/components/richText/RenderRichText.astro
import RichTextFieldBlockItem from './RichTextFieldBlockItem.astro';
import type { ApiBlockItemModel, RichTextGenericElementModel, RichTextRootElementModel, RichTextTextElementModel } from "@/api/umbraco";
import RenderRichTextComponent from './RenderRichText.astro';
interface Props {
node: RichTextGenericElementModel | RichTextRootElementModel | RichTextTextElementModel | null | undefined;
blocks: ApiBlockItemModel[] | null | undefined;
}
const { node, blocks } = Astro.props;
if (!node) return null;
const isText = node.tag === '#text';
const textNode = isText ? node as RichTextTextElementModel : null;
const isRoot = node.tag === '#root';
const rootNode = isRoot ? node as RichTextRootElementModel : null;
const isBlock = node.tag === 'umb-rte-block';
const blockNode = isBlock ? node as RichTextGenericElementModel : null;
const block = isBlock ? blocks?.find((b) => b.content && b.content.id === blockNode?.attributes['content-id']) : null;
const isGeneric = !isText && !isRoot && !isBlock;
const genericNode = isGeneric ? node as RichTextGenericElementModel : null;
const GenericTag = genericNode?.tag || 'div';
---
{isText && textNode?.text}
{isRoot && rootNode?.elements.map((child, i) =>
<RenderRichTextComponent node={child} blocks={blocks} />)}
{isBlock && <RichTextFieldBlockItem block={block} />}
{isGeneric && (
<GenericTag {...genericNode?.attributes}>
{genericNode?.elements.map((child, i) =>
<RenderRichTextComponent node={child} blocks={blocks} />)}
</GenericTag>
)}
set:html
. – mb21 Commented Feb 5 at 8:44