Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • npack 57 posts 323 karma points
    Oct 07, 2024 @ 20:50
    npack
    0

    v14 - Umbraco Flavored Markup Component - Simple Example?

    I want to do some simple string replacement on a block label.

    For a typical block label we do something like

    {= bodyText}
    

    I want to do something like

    {= bodyText.replace('foo', 'bar')}
    

    I've been been reading in the docs about custom UFM Components https://docs.umbraco.com/umbraco-cms/reference/umbraco-flavored-markdown#custom-ufm-components

    I've got a custom typescript file now with a render method that gets called when I use my custom token instead of the equals sign

    {% bodyText}
    

    but dang, I don't know how to get the property value to start with.

    import { UmbUfmComponentBase } from '@umbraco-cms/backoffice/ufm';
    import type { UfmToken } from '@umbraco-cms/backoffice/ufm';
    
    export class MyCustomUfmComponent extends UmbUfmComponentBase {
        render(token: UfmToken) {
    
            if (!token.text) return;
    
            const myPropertyValue = ?; // How do I get my property value.
            return `<ufm-custom-component text="${myPropertyValue.replace('foo', 'barr')}"></ufm-custom-component>`;
    
        }
    }
    

    The base class, UmbUfmComponentBase, and its parent class UmbUfmComponentApi, seem to provide some ability to query the content api but I can't make sense of it.

    I feel like I'm sooo close.

  • Huw Reddick 1929 posts 6717 karma points MVP 2x c-trib
    Oct 09, 2024 @ 13:06
    Huw Reddick
    0

    you need to import the context e.g. for Block Grid you would import

    UMB_BLOCK_ENTRY_CONTEXT 
    
  • Ibrahim Nada 22 posts 168 karma points c-trib
    Oct 09, 2024 @ 16:54
    Ibrahim Nada
    100

    as per my pull request here

    https://github.com/umbraco/Umbraco.CMS.Backoffice/pull/2345/files (hope fully it will get merged soon)

    by the question body you provided, you're half way there.

    you will need to consume UMB_BLOCK_ENTRY_CONTEXT. but there is a small problem is that UmbUfmComponentBase can not consume contexts so you need to create two element types

    one for the UFM and one for the context consumption and those would be in my Pull request

    • block-list-content-expression.component.ts for the UFM
    • block-list-content-expression.element.ts for the consumption which is used isnide of the first one (block-list-content-expression.component.ts)

    now ,

    because the block-list-content-expression.element.ts inherit UmbUfmElementBase its already has a property called value

    what ever you put in there will be displayed in the block label so you can do something like this

        this.consumeContext(UMB_BLOCK_ENTRY_CONTEXT, async (context) => {
    const content = await context.contentValues();
    this.value = content.name;
    
    
        });
    }
    

    and then youre done , take my Pull request as reference

  • npack 57 posts 323 karma points
    Oct 09, 2024 @ 19:43
    npack
    1

    Thank you for taking the time to reply! Ibrahim, it seems like this could be done with your regular expression component once it is merged in. I will watch for that.

    I was able to get my custom component working with your explanation, pull request, and from the source code of the vanilla ufm label. Some explanation on why this is all necessary was found in a recent discord chat https://discord-chats.umbraco.com/t/23104860/context-api-umbraco-cms and another possible solution https://discord-chats.umbraco.com/t/23111780/but-i-do-not-think-you-need-to-do-any-different-for-your-ufm

    Some gotchas: It seems my element tag had to start with the letters "ufm", i.e.

    <ufm-pdf-block-label>
    

    I couldn't put a slash in my label right before my token or it didn't work.

    "/{% bodyText}" doesn't work but "{% bodyText}" does
    

    The link between the element and the component seems to be based of the element name, which makes sense, but it also appears that this import inside the component is critical, even though I don't understand what it does. In my mind, imports import classes that can later be used but this syntax doesn't do that and it references a .js instead of .ts file. Maybe somebody can explain what this does.

    import './pdf-block-label-element.js';
    

    Here is my extension registration, from umbraco-package.json in the "extensions" section

    {
      "type": "ufmComponent",
      "alias": "MyProject.PdfBlockLabel",
      "name": "PDF Path",
      "api": "/App_Plugins/CustomBlockLabels/pdf-block-label.js",
      "meta": {
        "marker": "%"
      }
    },
    

    Here is the referenced component

    import { UmbUfmComponentBase } from '@umbraco-cms/backoffice/ufm';
    import type { UfmToken } from '@umbraco-cms/backoffice/ufm';
    
    
    import './pdf-block-label-element.js';
    
    export class PdfBlockLabel extends UmbUfmComponentBase {
        render(token: UfmToken) {
            if (!token.text) return;
    
            const attributes = super.getAttributes(token.text);
            return `<ufm-pdf-block-label ${attributes}></ufm-pdf-block-label>`;
        }
    }
    export { PdfBlockLabel as api };
    

    And here is the element

        import { UmbUfmElementBase } from '@umbraco-cms/backoffice/ufm';
        import { customElement, property } from '@umbraco-cms/backoffice/external/lit';
        import { UMB_BLOCK_ENTRY_CONTEXT } from '@umbraco-cms/backoffice/block';
    
        const elementName = 'ufm-pdf-block-label';
    
        @customElement(elementName)
        export class PdfBlockLabelElement extends UmbUfmElementBase {
            @property()
            alias?: string;
    
            constructor() {
                super();
                this.consumeContext(UMB_BLOCK_ENTRY_CONTEXT, (context) => {
                    this.observe(
                        context.content,
                        (value) => {
    
                            if (this.alias !== undefined && value !== undefined && typeof value === 'object') {
                                const record = (value as Record<string, unknown>)[this.alias];
    
                                // Whatever you want to do to the value here -- in my case lowercase with some string replacement.
                                this.value = record.toString().toLowerCase().replace(" ", "-"); 
    
    
                            } else {
                                this.value = value;
                            }
    
                        },
                        'observeValue',
                    );
                });
            }
        }
    
        export { PdfBlockLabelElement as element };
    
        declare global {
            interface HTMLElementTagNameMap {
                [elementName]: PdfBlockLabelElement;
            }
        }
    
  • npack 57 posts 323 karma points
    6 days ago
    npack
    0

    With v15 this code doesn't work anymore since context.content isn't public or even an observable variable anymore.

    Any ideas Niels Lyngs?

    constructor() {
                super();
                this.consumeContext(UMB_BLOCK_ENTRY_CONTEXT, (context) => {
                    this.observe(
                        **context.content,**
    
  • Ibrahim Nada 22 posts 168 karma points c-trib
    6 days ago
    Ibrahim Nada
    100

    Try something like this :

    this.consumeContext(UMB_BLOCK_ENTRY_CONTEXT, async (context) => {
            const content = await context.contentValues();
            this.observe(
                content,
                (value) => {
    
                                             /// use value here
    
    
                },
                'observeValue',
            );
        });
    
  • npack 57 posts 323 karma points
    5 days ago
    npack
    0

    Thanks Ibrahim!

    That is what I needed. Since I was in a constructor I had to wrap it in a .then(() => {}); block but it seems to be working again.

    Much appreciated.

    Note: the new UFM 'Filters' get close to providing what I need here but no '.replace()' filter yet

Please Sign in or register to post replies

Write your reply to:

Draft