AdaptiveCards icon indicating copy to clipboard operation
AdaptiveCards copied to clipboard

[Rendering] Add Missing Properties to Default Types

Open davideberlein opened this issue 6 months ago • 0 comments

Target Platforms

NodeJS

SDK Version

3.0.5

Application Name

Custom UI

Problem Description

I have an application that renderes AdaptiveCards in my own UI. I have been using some default adaptive card examples from the new designer: https://adaptivecards.microsoft.com/designer

A lot of them use the targetWidth property. When rendering them using the Microsoft Adaptive Card JS SDK it looks like it doesn't support this property.

So I created my own custom Image Element that simply extends the default Image and overrides the existing default Image element according to the MS Docs

While my own renderer is now being called for images, the new property I added isn't being processed:

static readonly targetWidthProperty = new StringProperty(AdaptiveCards.Versions.v1_0, "targetWidth");

@AdaptiveCards.property(CustomElement.targetWidthProperty)
targetWidth?: string;

If I register my component under a different name (e.g. CustomImage) and rename the Images in the json config to CustomImage by new property is processed properly.

It seems like the schema of default elements is somehow cached. If I try to override the Image type the populateSchema() isn't called, if I use my own CustomImage it's called. I've tried everything, using my own SerializationContext etc. but can't find a solution.

So how are we supposed to add missing features to the default components?

Screenshots

No response

Card JSON

{
    "type": "AdaptiveCard",
    "$schema": "https://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.5",
    "body": [
        {
            "type": "TextBlock",
            "text": "Time off request",
            "wrap": true,
            "size": "Large",
            "weight": "Bolder",
            "spacing": "None"
        },
        {
            "type": "Image",
            "url": "https://raw.githubusercontent.com/OfficeDev/Microsoft-Teams-Adaptive-Card-Samples/main/samples/time-off-request/assets/hero-image-default.png",
            "style": "RoundedCorners",
            "targetWidth": "Standard"
        },
        {
            "type": "Image",
            "targetWidth": "Wide",
            "url": "https://raw.githubusercontent.com/OfficeDev/Microsoft-Teams-Adaptive-Card-Samples/main/samples/time-off-request/assets/hero-wide.png",
            "style": "RoundedCorners"
        }
    ]
}

Sample Code Language

TypeScript

Sample Code

import * as AdaptiveCards from "adaptivecards";
import {Image, SerializableObjectSchema, StringProperty} from "adaptivecards";
import {PropertyBag} from "adaptivecards/src/serialization";

export default class AdaptiveCardApi {

    private readonly serializationContext: AdaptiveCards.SerializationContext;

    constructor() {
        // Create a custom registry for elements
        let elementRegistry = new AdaptiveCards.CardObjectRegistry<AdaptiveCards.CardElement>();

        // Populate it with the default set of elements
        AdaptiveCards.GlobalRegistry.populateWithDefaultElements(elementRegistry);

        // Parse a card payload using the custom registry
        this.serializationContext = new AdaptiveCards.SerializationContext();
        this.serializationContext.setElementRegistry(elementRegistry);

        const serializationContext = this.serializationContext;

        class CustomElement extends OriginalClass {
            //#region Schema
            static readonly targetWidthProperty = new StringProperty(AdaptiveCards.Versions.v1_0, "targetWidth");

            @AdaptiveCards.property(CustomElement.targetWidthProperty)
            targetWidth?: string;
            //#endregion

            protected populateSchema(schema: SerializableObjectSchema) {
                // Never logged as never being called
                console.log("populateSchema", this.getJsonTypeName(), 'schema', schema);
                super.populateSchema(schema);
            }

            protected internalRender(): HTMLElement | undefined {
                const renderedElement = super.internalRender();
                // Add our data attribute if targetWidth is present on the model.
                if (renderedElement && this.targetWidth) {
                    renderedElement.classList.add(`ac-${this.targetWidth}`);
                }

                return renderedElement;
            }
        }

        elementRegistry.register("Image", CustomElement);
    }

    public createCard(cardConfig: PropertyBag) {
        // Create an AdaptiveCard instance
        const adaptiveCard = new AdaptiveCards.AdaptiveCard();

        // Parse the card payload
        adaptiveCard.parse(cardConfig, this.serializationContext);

        // Render the card to an HTML element:
        return adaptiveCard.render();
    }
}

davideberlein avatar Jul 29 '25 11:07 davideberlein