Custom Block Preview - Clicking Preview opens EditBlock

Hiya :waving_hand:
We are currently trying to update our custom block previews (around 40 or so) that allows the content editors to click on the preview to open the edit block dialog, instead of having specifically clicking the edit pencil icon to open the dialog.

We are able to do this with code like so by using the UMB_BLOCK_ENTRY_CONTEXT

Simple Example

Example Block Preview

import { UMB_BLOCK_ENTRY_CONTEXT, 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, property, unsafeHTML } from "@umbraco-cms/backoffice/external/lit";
import { BackofficeCssStyles } from "../../common/styles/backoffice-css-styles";
import { RTEData } from "../../common/types/rte";

interface ParagraphBlockContent extends UmbBlockDataType {
    richText: RTEData;
}

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

    #blockEntryCtx?: typeof UMB_BLOCK_ENTRY_CONTEXT.TYPE;

    constructor() {
        super();

        this.consumeContext(UMB_BLOCK_ENTRY_CONTEXT,(blockEntryCtx) => {
            this.#blockEntryCtx = blockEntryCtx;
        });
    }

    // Ideally dont want to keep copying this into every block preview
    // Along with the consumeContext above in the ctor
    editBlock() {
        this.#blockEntryCtx?.edit();
    }

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

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

    @property({attribute: false})
    settings?: UmbBlockDataType;
    
    render() {
        return html`
            <fieldset>
                <legend>${this.label ?? 'Paragraph'}</legend>
                <div @click=${this.editBlock}>
                    ${unsafeHTML(this.content?.richText.markup)}
                </div>
            </fieldset>
        `;
    }

    static styles = [
        BackofficeCssStyles,
        css ``
    ]
}

export default ParagraphBlockViewElement;

declare global {
    interface HTMLElementTagNameMap {
        'paragraph-block-view': ParagraphBlockViewElement;
    }
}

Rather than doing this lots of times over, we are hoping to write this once and the 40 or so Block Preview elements can then have access to this method to launch the edit dialog. The current thought process is to use a mixin approach.

Mixin approach

Mixin code

import { UMB_BLOCK_ENTRY_CONTEXT } from "@umbraco-cms/backoffice/block";
import { UmbElement } from "@umbraco-cms/backoffice/element-api";
import { BlockPreviewService } from "../../api";

type Constructor<T = {}> = new (...args: any[]) => T;

export const BlockPreviewMixin = <TBase extends Constructor<UmbElement>>(Base: TBase) =>
  class extends Base {
    protected _blockEntryCtx?: typeof UMB_BLOCK_ENTRY_CONTEXT.TYPE;

    connectedCallback() {
        super.connectedCallback();

        this.consumeContext(UMB_BLOCK_ENTRY_CONTEXT, (blockEntryCtx) => {
            console.log("BlockPreviewMixin: Consumed UMB_BLOCK_ENTRY_CONTEXT", blockEntryCtx);
            this._blockEntryCtx = blockEntryCtx;
        });
    }

    editBlock() {
        console.log("BlockPreviewMixin: editBlock called");
        this._blockEntryCtx?.edit();
    }

    // *****************************************************
    // TODO: HELP Why do I need this dummy async function
    // In order for the block preview to be rendered ?
    // *****************************************************
    async dummyAsyncFunction() {
        await BlockPreviewService.getAllCouncilConfigurations();
    }
  };

Updated Block Preview

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, property, unsafeHTML } from "@umbraco-cms/backoffice/external/lit";
import { BackofficeCssStyles } from "../../common/styles/backoffice-css-styles";
import { RTEData } from "../../common/types/rte";
import { BlockPreviewMixin } from "./BlockPreviewMixin";

interface ParagraphBlockContent extends UmbBlockDataType {
    richText: RTEData;
}

// Our NEW BlockPreviewMixin wraps UmbElementMixin(LitElement)
@customElement('paragraph-block-view')
export class ParagraphBlockViewElement extends BlockPreviewMixin(UmbElementMixin(LitElement)) implements UmbBlockEditorCustomViewElement {

    constructor() {
        super();
    }

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

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

    @property({attribute: false})
    settings?: UmbBlockDataType;
    
    render() {
        return html`
            <fieldset>
                <legend>${this.label ?? 'Paragraph'}</legend>
                <div @click=${this.editBlock}>
                    ${unsafeHTML(this.content?.richText.markup)}
                </div>
            </fieldset>
        `;
    }

    static styles = [
        BackofficeCssStyles,
        css ``
    ]
}

export default ParagraphBlockViewElement;

declare global {
    interface HTMLElementTagNameMap {
        'paragraph-block-view': ParagraphBlockViewElement;
    }
}

Problem I need help with

The mixin approach only works if I have a method that is async and awaits a function. Even if I don’t use or consume it in my block preview. Without the async method then the preview for the block element does not appear at all.

  • Does anyone have any smart ideas or reasons as to why this is happening?

  • Alternatively perhaps you have a nicer way to have this code in one place to share across all block previews ?

Thanks,
Warren :smiling_face:

Hi @warren

In the case of wanting to let the user open the edit modal from within your Custom View, then you can also use the path we generate for you. So no need for consuming contexts or any thing, just inserting an A-tag with the path.

Here is the code snippets needed for that:

Declare the config property, as you will be using a property from it (This is something we send to all Custom Views)

@property({ attribute: false })
config?: UmbBlockEditorCustomViewConfiguration;

In your rendering you can use it like this:

<a href=${this.config?.editContentPath ?? ''}>
...
1 Like

Thanks @nielslyngsoe I missed that config property !
This is a useful approach to solve our problem, I have just added some CSS styling in the component like so

CSS for < a > tag

a.edit {
    display:block;
    text-decoration: none;
    color: inherit;
}

Full example

import { UmbBlockDataType } from "@umbraco-cms/backoffice/block";
import { UmbBlockEditorCustomViewConfiguration, UmbBlockEditorCustomViewElement } from "@umbraco-cms/backoffice/block-custom-view";
import { UmbElementMixin } from "@umbraco-cms/backoffice/element-api";
import { css, customElement, html, LitElement, property, unsafeHTML } from "@umbraco-cms/backoffice/external/lit";
import { BackofficeCssStyles } from "../../common/styles/backoffice-css-styles";
import { RTEData } from "../../common/types/rte";

interface ParagraphBlockContent extends UmbBlockDataType {
    richText: RTEData;
}

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

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

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

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

    @property({ attribute: false })
    config?: UmbBlockEditorCustomViewConfiguration;
    
    render() {
        return html`
            <fieldset>
                <legend>${this.label ?? 'Paragraph'}</legend>
                <a href=${this.config?.editContentPath ?? ''} class="edit">
                    ${unsafeHTML(this.content?.richText.markup)}
                </a>
            </fieldset>
        `;
    }

    static styles = [
        BackofficeCssStyles,
        css `
            a.edit {
                display:block;
                text-decoration: none;
                color: inherit;
            }
        `
    ]
}

export default ParagraphBlockViewElement;

declare global {
    interface HTMLElementTagNameMap {
        'paragraph-block-view': ParagraphBlockViewElement;
    }
}