Custom Block View: How best to resolve content from a MNTP Unique?

Heya,
I am trying to think of the best approach of building a custom block view for Bellissima that has some properties set on the top level of the block.


Shape of JSON - Content property on Block

{
    "header": "About",
    "introduction": {
        "markup": "<p>Lorem Lipsum</p>",
        "blocks": {
            "layout": {},
            "contentData": [],
            "settingsData": [],
            "expose": []
        }
    },
    "cardItems": {
        "layout": {
            "Umbraco.BlockList": [
                {
                    "$type": "BlockListLayoutItem",
                    "contentUdi": "umb://element/b5f91f1591664b989b402da255daa171",
                    "settingsUdi": null,
                    "contentKey": "b5f91f15-9166-4b98-9b40-2da255daa171",
                    "settingsKey": null
                },
                {
                    "$type": "BlockListLayoutItem",
                    "contentUdi": "umb://element/62fb62851b234d34a4323cbcc1733feb",
                    "settingsUdi": null,
                    "contentKey": "62fb6285-1b23-4d34-a432-3cbcc1733feb",
                    "settingsKey": null
                },
                {
                    "$type": "BlockListLayoutItem",
                    "contentUdi": "umb://element/c58024105eb74cb2aa40ada0369d5db8",
                    "settingsUdi": null,
                    "contentKey": "c5802410-5eb7-4cb2-aa40-ada0369d5db8",
                    "settingsKey": null
                },
                {
                    "$type": "BlockListLayoutItem",
                    "contentUdi": "umb://element/e07d5888e4634c6dac0e89008a24a96d",
                    "settingsUdi": null,
                    "contentKey": "e07d5888-e463-4c6d-ac0e-89008a24a96d",
                    "settingsKey": null
                },
                {
                    "$type": "BlockListLayoutItem",
                    "contentUdi": "umb://element/0b7d00e0024847308c6643e10d4c118e",
                    "settingsUdi": null,
                    "contentKey": "0b7d00e0-0248-4730-8c66-43e10d4c118e",
                    "settingsKey": null
                },
                {
                    "$type": "BlockListLayoutItem",
                    "contentUdi": "umb://element/d2fc5d2bd6244acc92b674f1275c4c85",
                    "settingsUdi": null,
                    "contentKey": "d2fc5d2b-d624-4acc-92b6-74f1275c4c85",
                    "settingsKey": null
                },
                {
                    "$type": "BlockListLayoutItem",
                    "contentUdi": "umb://element/7bac1321f5254523b05260c3be4be86b",
                    "settingsUdi": null,
                    "contentKey": "7bac1321-f525-4523-b052-60c3be4be86b",
                    "settingsKey": null
                },
                {
                    "$type": "BlockListLayoutItem",
                    "contentUdi": "umb://element/e7e5ea674ad04ea6a25f66b09654d5ed",
                    "settingsUdi": null,
                    "contentKey": "e7e5ea67-4ad0-4ea6-a25f-66b09654d5ed",
                    "settingsKey": null
                }
            ]
        },
        "contentData": [
            {
                "contentTypeKey": "c5714a85-0b3b-4c4e-888a-e34b9b25bd98",
                "udi": null,
                "key": "b5f91f15-9166-4b98-9b40-2da255daa171",
                "values": [
                    {
                        "editorAlias": "Umbraco.DateTime",
                        "culture": null,
                        "segment": null,
                        "alias": "scheduleDisplay",
                        "value": ""
                    },
                    {
                        "editorAlias": "Umbraco.TrueFalse",
                        "culture": null,
                        "segment": null,
                        "alias": "hidden",
                        "value": false
                    },
                    {
                        "editorAlias": "Umbraco.MultiNodeTreePicker",
                        "culture": null,
                        "segment": null,
                        "alias": "cardItem",
                        "value": [
                            {
                                "type": "document",
                                "unique": "00118592-176b-44a2-8e9e-42d6dcc37be9"
                            }
                        ]
                    },
                    {
                        "editorAlias": "Umbraco.Community.Contentment.DataList",
                        "culture": null,
                        "segment": null,
                        "alias": "size",
                        "value": "Small"
                    }
                ]
            },
            {
                "contentTypeKey": "c5714a85-0b3b-4c4e-888a-e34b9b25bd98",
                "udi": null,
                "key": "62fb6285-1b23-4d34-a432-3cbcc1733feb",
                "values": [
                    {
                        "editorAlias": "Umbraco.DateTime",
                        "culture": null,
                        "segment": null,
                        "alias": "scheduleDisplay",
                        "value": ""
                    },
                    {
                        "editorAlias": "Umbraco.TrueFalse",
                        "culture": null,
                        "segment": null,
                        "alias": "hidden",
                        "value": false
                    },
                    {
                        "editorAlias": "Umbraco.MultiNodeTreePicker",
                        "culture": null,
                        "segment": null,
                        "alias": "cardItem",
                        "value": [
                            {
                                "type": "document",
                                "unique": "f77bcaac-a21c-4d8e-a29d-3651183338d4"
                            }
                        ]
                    },
                    {
                        "editorAlias": "Umbraco.Community.Contentment.DataList",
                        "culture": null,
                        "segment": null,
                        "alias": "size",
                        "value": "Small"
                    }
                ]
            },
            {
                "contentTypeKey": "c5714a85-0b3b-4c4e-888a-e34b9b25bd98",
                "udi": null,
                "key": "d2fc5d2b-d624-4acc-92b6-74f1275c4c85",
                "values": [
                    {
                        "editorAlias": "Umbraco.DateTime",
                        "culture": null,
                        "segment": null,
                        "alias": "scheduleDisplay",
                        "value": ""
                    },
                    {
                        "editorAlias": "Umbraco.TrueFalse",
                        "culture": null,
                        "segment": null,
                        "alias": "hidden",
                        "value": false
                    },
                    {
                        "editorAlias": "Umbraco.MultiNodeTreePicker",
                        "culture": null,
                        "segment": null,
                        "alias": "cardItem",
                        "value": [
                            {
                                "type": "document",
                                "unique": "10ca3056-06f2-4788-8cf3-50d7d4296cc5"
                            }
                        ]
                    },
                    {
                        "editorAlias": "Umbraco.Community.Contentment.DataList",
                        "culture": null,
                        "segment": null,
                        "alias": "size",
                        "value": "Small"
                    }
                ]
            },
            {
                "contentTypeKey": "c5714a85-0b3b-4c4e-888a-e34b9b25bd98",
                "udi": null,
                "key": "e07d5888-e463-4c6d-ac0e-89008a24a96d",
                "values": [
                    {
                        "editorAlias": "Umbraco.DateTime",
                        "culture": null,
                        "segment": null,
                        "alias": "scheduleDisplay",
                        "value": ""
                    },
                    {
                        "editorAlias": "Umbraco.TrueFalse",
                        "culture": null,
                        "segment": null,
                        "alias": "hidden",
                        "value": false
                    },
                    {
                        "editorAlias": "Umbraco.MultiNodeTreePicker",
                        "culture": null,
                        "segment": null,
                        "alias": "cardItem",
                        "value": [
                            {
                                "type": "document",
                                "unique": "d2652cd2-2b2d-40ff-9438-8eac10a96ba0"
                            }
                        ]
                    },
                    {
                        "editorAlias": "Umbraco.Community.Contentment.DataList",
                        "culture": null,
                        "segment": null,
                        "alias": "size",
                        "value": "Medium"
                    }
                ]
            },
            {
                "contentTypeKey": "c5714a85-0b3b-4c4e-888a-e34b9b25bd98",
                "udi": null,
                "key": "c5802410-5eb7-4cb2-aa40-ada0369d5db8",
                "values": [
                    {
                        "editorAlias": "Umbraco.DateTime",
                        "culture": null,
                        "segment": null,
                        "alias": "scheduleDisplay",
                        "value": ""
                    },
                    {
                        "editorAlias": "Umbraco.TrueFalse",
                        "culture": null,
                        "segment": null,
                        "alias": "hidden",
                        "value": false
                    },
                    {
                        "editorAlias": "Umbraco.MultiNodeTreePicker",
                        "culture": null,
                        "segment": null,
                        "alias": "cardItem",
                        "value": [
                            {
                                "type": "document",
                                "unique": "bb56ca7c-7b91-47a9-99a3-f1d642452275"
                            }
                        ]
                    },
                    {
                        "editorAlias": "Umbraco.Community.Contentment.DataList",
                        "culture": null,
                        "segment": null,
                        "alias": "size",
                        "value": "Small"
                    }
                ]
            },
            {
                "contentTypeKey": "c5714a85-0b3b-4c4e-888a-e34b9b25bd98",
                "udi": null,
                "key": "e7e5ea67-4ad0-4ea6-a25f-66b09654d5ed",
                "values": [
                    {
                        "editorAlias": "Umbraco.DateTime",
                        "culture": null,
                        "segment": null,
                        "alias": "scheduleDisplay",
                        "value": ""
                    },
                    {
                        "editorAlias": "Umbraco.TrueFalse",
                        "culture": null,
                        "segment": null,
                        "alias": "hidden",
                        "value": false
                    },
                    {
                        "editorAlias": "Umbraco.MultiNodeTreePicker",
                        "culture": null,
                        "segment": null,
                        "alias": "cardItem",
                        "value": [
                            {
                                "type": "document",
                                "unique": "6522a6c9-4427-40eb-a05b-43dd7a2498a7"
                            }
                        ]
                    },
                    {
                        "editorAlias": "Umbraco.Community.Contentment.DataList",
                        "culture": null,
                        "segment": null,
                        "alias": "size",
                        "value": "Small"
                    }
                ]
            },
            {
                "contentTypeKey": "c5714a85-0b3b-4c4e-888a-e34b9b25bd98",
                "udi": null,
                "key": "7bac1321-f525-4523-b052-60c3be4be86b",
                "values": [
                    {
                        "editorAlias": "Umbraco.DateTime",
                        "culture": null,
                        "segment": null,
                        "alias": "scheduleDisplay",
                        "value": ""
                    },
                    {
                        "editorAlias": "Umbraco.TrueFalse",
                        "culture": null,
                        "segment": null,
                        "alias": "hidden",
                        "value": false
                    },
                    {
                        "editorAlias": "Umbraco.MultiNodeTreePicker",
                        "culture": null,
                        "segment": null,
                        "alias": "cardItem",
                        "value": [
                            {
                                "type": "document",
                                "unique": "1210af9b-73fc-4090-b9e5-010e16d00495"
                            }
                        ]
                    },
                    {
                        "editorAlias": "Umbraco.Community.Contentment.DataList",
                        "culture": null,
                        "segment": null,
                        "alias": "size",
                        "value": "Small"
                    }
                ]
            },
            {
                "contentTypeKey": "c5714a85-0b3b-4c4e-888a-e34b9b25bd98",
                "udi": null,
                "key": "0b7d00e0-0248-4730-8c66-43e10d4c118e",
                "values": [
                    {
                        "editorAlias": "Umbraco.DateTime",
                        "culture": null,
                        "segment": null,
                        "alias": "scheduleDisplay",
                        "value": ""
                    },
                    {
                        "editorAlias": "Umbraco.TrueFalse",
                        "culture": null,
                        "segment": null,
                        "alias": "hidden",
                        "value": false
                    },
                    {
                        "editorAlias": "Umbraco.MultiNodeTreePicker",
                        "culture": null,
                        "segment": null,
                        "alias": "cardItem",
                        "value": [
                            {
                                "type": "document",
                                "unique": "9ccf31ec-9d20-4e86-9c15-25f97c299428"
                            }
                        ]
                    },
                    {
                        "editorAlias": "Umbraco.Community.Contentment.DataList",
                        "culture": null,
                        "segment": null,
                        "alias": "size",
                        "value": "Small"
                    }
                ]
            }
        ],
        "settingsData": [],
        "expose": [
            {
                "contentKey": "b5f91f15-9166-4b98-9b40-2da255daa171",
                "culture": null,
                "segment": null
            },
            {
                "contentKey": "62fb6285-1b23-4d34-a432-3cbcc1733feb",
                "culture": null,
                "segment": null
            },
            {
                "contentKey": "d2fc5d2b-d624-4acc-92b6-74f1275c4c85",
                "culture": null,
                "segment": null
            },
            {
                "contentKey": "e07d5888-e463-4c6d-ac0e-89008a24a96d",
                "culture": null,
                "segment": null
            },
            {
                "contentKey": "c5802410-5eb7-4cb2-aa40-ada0369d5db8",
                "culture": null,
                "segment": null
            },
            {
                "contentKey": "e7e5ea67-4ad0-4ea6-a25f-66b09654d5ed",
                "culture": null,
                "segment": null
            },
            {
                "contentKey": "7bac1321-f525-4523-b052-60c3be4be86b",
                "culture": null,
                "segment": null
            },
            {
                "contentKey": "0b7d00e0-0248-4730-8c66-43e10d4c118e",
                "culture": null,
                "segment": null
            }
        ]
    }
}

I can easily grab the following information to use in the HTML of the component

  • header :white_check_mark:
  • introduction :white_check_mark:

For the cardItems property at this level, it is an Umbraco Block List which the JSON object contains:

  • layout
  • contentData
  • settingsData
  • expose

Again I am able to get info and values out of the items in the cardItems from inside cardItems.contentData such as

  • scheduleDisplay :white_check_mark:
  • hidden :white_check_mark:
  • size :white_check_mark:

But the property cardItem is a MNTP picker set to allow only pick one content node and contains two properties type and unique

I am stuck at this point, on how best to get properties/values of the picked node in the MNTP as I only have a GUID/key of the picked document.

Ideas…

My thought to this problem is to get the list of contentKey stored in expose array at the top level and call a custom C# API controller to get the content server side and resolve the picked content node stored in the content picker and return my own shape of data.

Has anyone else resolved this?

Does anyone else have any other suggestions or ideas on how to resolve this level of nesting of content and properties inside an element type?

TL:DR version

  • Unsure how to resolve a MNTP picked node to get more data/properties for rendering

Hello @warren,

As per most of my responses, please don’t take this as gospel, I’ve been trying all sorts of stuff, and the last thing I want to do is lead people up the wrong garden path.

However. If you see this reply on this post:

Using the DocumentItemRepository might help you get the content via the unique property for your card item.

import { UmbDocumentItemRepository } from '@umbraco-cms/backoffice/document';

and

let { data } = await this.#itemRepository.requestItems( [unique] );

I might be wrong, but .requestItems is looking for an array of unique’s.

Thank you,
Rob

2 Likes

@warren As @sm-rob suggests, try the UmbDocumentItemRepository. That’ll give you basic meta-data of a document, but if you need the full model (e.g. to get other property data), try UmbDocumentDetailRepository instead.

One note about your initial idea, the expose property/object holds references to the block item keys, (e.g. element types), not the content picker’s document/node. You’d need to drill down to a block’s cardItem property and get the unique from the value array.

1 Like

Yes the expose I was going to send them off to the server and then use that to get the outer element type with its properties and then get the data I need with content cache for the MNTP.

But when back at my desk I will try using the UmbDocumebtDetailRepository. Do you think it would be best to split each child item into a new web component that has a property for the unique and deal with it that way?

It’s entirely your call. :grinning_face:

Breaking the logic down into more granular web-components is good for maintenance/reusability… but comes with the extra development effort upfront. So if you’re going to reuse these in other custom block views or other projects, then it makes sense. :+1:

Yeh trying to see if I can be semi clever somehow and make the component semi-generic and that the default slot could be used somehow as the way to template it.

As the scenario of an element type having a property that then has a MNTP on the element, is a common pattern for this project/client.

So just trying to think what is the best most effective way.

OK I have been back to this problem today and new’ing up UmbDocumentDetailRepository has been useful to get the values of the picked node in the MNTP, so thanks @leekelleher and @sm-rob :umbraco-heart:

Data Structure

Just a reminder of this client’s data structure:

Card Block Element → Card Items (Block List) → Card Item (MNTP) → Title (textString)

Screenshot

Code Review

I would love a code review of the approach, before I do any tidying up.
Currently this feels very complex to do this :frowning:

  • Am I getting the culture from the right place - so so so many contexts !!
    • Should I be getting it from UMB_CONTENT_PROPERTY_DATASET_CONTEXT or somewhere else?
  • When I was exploring contexts I noticed there was nice ways to get property values, is there a way I can use this when nested further inside the block list or down with the picked node in the MNTP?

Code

I have the two following classes:

  • cards.ts - is what is used to display the top level properties on the card element
  • card-item-element.ts - is used for each item in the blocklist property

cards.ts

import { UmbBlockDataType } from "@umbraco-cms/backoffice/block";
import { UmbBlockEditorCustomViewElement } from "@umbraco-cms/backoffice/block-custom-view";
import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api";
import { css, customElement, html, LitElement, nothing, property, unsafeHTML } from "@umbraco-cms/backoffice/external/lit";
import { CommonStyles } from "./common-styles";
import './card-item-element';

interface CardsBlockContent extends UmbBlockDataType {
    header?: string;
    introduction?: {
        markup?: string;
    }
    cardItems?: {
        contentData?: [],
        layout?: {
            "Umbraco.BlockList": [
                {
                    contentKey: string;
                }
            ];
        };
    }
}


@customElement('cards-block-view')
export class CardsBlockViewElement extends UmbElementMixin(LitElement) implements UmbBlockEditorCustomViewElement {

    constructor() {
        super();
    }

    @property({attribute: false})
    label?: string;

    @property({attribute: false})
    content?: CardsBlockContent;

    @property({attribute: false})
    settings?: UmbBlockDataType;

    
    render() {
        const layout = this.content?.cardItems?.layout?.['Umbraco.BlockList'] || [];
        const cardItemsData = this.content?.cardItems?.contentData || [];

        return html`
            <fieldset>
                <legend>${this.label ?? 'Cards'}</legend>

                <div class="paddings lightgray">
                    ${this.content?.header ? 
                        html`<h2>${this.content.header}</h2>` 
                        : nothing
                    }

                    ${unsafeHTML(this.content?.introduction?.markup)}

                    <div class="cards-container">
                        ${layout.map((layoutItem) => {
                            const item = cardItemsData.find((cd:any) => cd.key === layoutItem.contentKey);
                            return item
                                ? html`<cards-card-item .item=${item}></cards-card-item>`
                                : nothing;
                        })}
                    </div>
                </div>
            </fieldset>
        `;
    }

    static styles = [
        CommonStyles,
        css `            
            h2 {
                margin: auto;
                margin-bottom: 20px;
            }

            .cards-container {
                display: flex;
                justify-content: space-between;
                flex-wrap: wrap;
                width: 100%;
            }

            cards-card-item {
                display: flex;
                flex-wrap: wrap;
                flex-direction: column;
                width: 25%;
                padding: 10px;

                background:grey;
                margin:10px 0;
            }
        `
    ]
}

export default CardsBlockViewElement;

declare global {
    interface HTMLElementTagNameMap {
        'cards-block-view': CardsBlockViewElement;
    }
}

card-item-element.ts

import { LitElement, css, html, customElement, property, nothing, state } from "@umbraco-cms/backoffice/external/lit";
import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api";
import { UmbDocumentDetailRepository } from "@umbraco-cms/backoffice/document";
import { UMB_CONTENT_PROPERTY_DATASET_CONTEXT } from "@umbraco-cms/backoffice/content";

@customElement('cards-card-item')
export class CardsCardBlockViewElement extends UmbElementMixin(LitElement) {

  #documentDetailRepo: UmbDocumentDetailRepository;

  constructor() {
    super();

    this.consumeContext(UMB_CONTENT_PROPERTY_DATASET_CONTEXT, (umbContentPropertyDatasetCtx) => {

      this.observe(umbContentPropertyDatasetCtx.culture, (culture) => {
        this.culture = culture ?? undefined;
      });
    });

    // New up UmbDocumentDetailRepository - so we can use it to fetch info about the picked items in the MNTP
    this.#documentDetailRepo = new UmbDocumentDetailRepository(this);
  }

  // Lit lifecycle event
  updated(changedProperties: Map<string, any>) {
    if (changedProperties.has('item')) {
      this.#getValues();
    }
  }

  @property({ attribute: false })
  item?: any;

  @state()
  itemKey?: string;

  @state()
  culture?: string;

  @state()
  scheduleDisplay?: string;

  @state()
  size?: string;

  @state()
  hidden: boolean = false;

  @state()
  mntpTitle?: string;

  async #getValues(){
    const values = this.item?.values ?? [];
    this.itemKey = this.item?.key;

    // Values at the top level of the block element
    this.scheduleDisplay = values.find((v: any) => v.alias === 'scheduleDisplay')?.value || nothing; // TODO: Render as proper date
    this.size = values.find((v: any) => v.alias === 'size')?.value || nothing;
    this.hidden = values.find((v: any) => v.alias === 'hidden')?.value ?? false;
    

    // This is a MNTP picker, so we need to get the first item in the array and then get the unique value from that item
    const cardItemMntp = values.find((v: any) => v.alias === 'cardItem')?.value;
    const cardItemMntpUnique = Array.isArray(cardItemMntp) && cardItemMntp.length > 0 ? cardItemMntp[0].unique : nothing;
    //console.log('cardItemMntpUnique', cardItemMntpUnique);

    // Go and get the picked item from the MNTP
    let { data: documentData  } = await this.#documentDetailRepo.requestByUnique(cardItemMntpUnique);
    console.log('document details', documentData);

    // Assign states to use in the template
    this.mntpTitle = documentData?.values.find((item: any) => item.alias === 'title' && item.culture === this.culture)?.value as string ?? nothing;
  }

  render() {
    return html`
       <div class="card-item">
            <uui-tag look="primary" color="default">Culture: ${this.culture}</uui-tag>
            <p>${this.itemKey}</p>
            <div>
                <img ng-src="{{item.card.cardDetails.ImageUrl}}" />
            </div>
            <h3>Title: ${this.mntpTitle}</h3>

            ${this.hidden
              ? html`<uui-tag look="primary" color="default">Hidden</uui-tag>`
              : nothing
            }

            <uui-tag look="primary" color="default">Size: ${this.size}</uui-tag>
            <uui-tag look="primary" color="default">Scheduled for Display: ${this.scheduleDisplay}</uui-tag>
        </div>
    `;
  }

  static styles = [
    css`
        :host {
          border: 2px solid red;
        }

        .card-item {
          border: 2px solid green;
        }

         h3 {
          margin: 0;
         }
    `];
}

export default CardsCardBlockViewElement;

declare global {
  interface HTMLElementTagNameMap {
    'cards-card-item': CardsCardBlockViewElement;
  }
}

Help

  • What would you change or improve - ignoring the awful CSS debug/styling :see_no_evil_monkey: