最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

Astro.JS Display HTML from JSON data content WITHOUT set:html - Stack Overflow

programmeradmin8浏览0评论

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
  • Not sure I understand "this requires rendering the json block as html including embedded objects". What embedded objects? You mean iframes and similar? Or nested elements in the tree structure? Either way, at some point it will have to be an HTML string, so I don't see the problem with set:html. – mb21 Commented Feb 5 at 8:44
  • @mb21 if you look in the above json, you can see a block of json that has a tag of "umb-rte-block". This is not standard html. I want to be able render that block using an astro component. If I were to use the set:html, I would need to render partial strings to html then figure out how to inject a component, which seems like it would be complex. You can see below for what I was trying to achieve. – J Fo Commented Feb 5 at 18:15
  • Yeah, sure, you can call astro components recursively. Or alternatively you could also call plain old functions recursively, which return HTML strings. – mb21 Commented Feb 6 at 12:37
Add a comment  | 

1 Answer 1

Reset to default 0

I 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:

  1. The import RenderRichTextComponent is a reference to itself. Thus it becomes a recursive component.
  2. 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>
)}
发布评论

评论列表(0)

  1. 暂无评论