'Backend (NodeJS) - Adaptivecard JSON to HTML Parser

What I'm trying to achieve?

  • A back-end NodeJS service which will receive an adaptive card JSON request and give a parsed HTML response.

Why I'm writing this service?

  • There is a requirement to send email to users and I don't want to write a separate HTML template for email when we already have a tons of adaptive card JSON templates which can be re-used.

What I tried for backend service?

  • Created a nodejs service
  • Installed node package: npm install adaptivecards
  • Wrote a sample program to parse a JSON adaptive card to HTML.

Problems I faced:

  • Very 1st problem I faced: render() function reported that there were no document, window and HTMLElement.

  • To fix above issue I installed jsdom and created a DOM object and assigned to global:

    var jsdom = require("jsdom");
    var JSDOM = jsdom.JSDOM;
    var html = `<!DOCTYPE html>
        <html>
            <body id="cardAdaptive"></body>
        </html>
    `;
    
    const DOM = new JSDOM(html);
    global.window = DOM.window;
    global.document = DOM.window.document;
    global.HTMLElement = DOM.window.HTMLElement;
    
  • Main problem: Program ran fine without any problem but the parsed HTML was incomplete. Please refer the images attached below.

Parser program (parser.js):

var jsdom = require("jsdom");
var JSDOM = jsdom.JSDOM;
var html = `<!DOCTYPE html>
    <html>
        <body id="cardAdaptive"></body>
    </html>
`;

const DOM = new JSDOM(html);
global.window = DOM.window;
global.document = DOM.window.document;
global.HTMLElement = DOM.window.HTMLElement;

var AdaptiveCards = require("adaptivecards");

var card = {
    "type": "AdaptiveCard",
    "version": "1.0",
    "body": [
        {
            "type": "Image",
            "url": "http://adaptivecards.io/content/adaptive-card-50.png"
        },
        {
            "type": "TextBlock",
            "text": "Hello **Adaptive Cards!**"
        }
    ],
    "actions": [
        {
            "type": "Action.OpenUrl",
            "title": "Learn more",
            "url": "http://adaptivecards.io"
        },
        {
            "type": "Action.OpenUrl",
            "title": "GitHub",
            "url": "http://github.com/Microsoft/AdaptiveCards"
        }
    ]
};

// Create an AdaptiveCard instance
var adaptiveCard = new AdaptiveCards.AdaptiveCard();

// Set its hostConfig property unless you want to use the default Host Config
// Host Config defines the style and behavior of a card
adaptiveCard.hostConfig = new AdaptiveCards.HostConfig({
    fontFamily: "Segoe UI, Helvetica Neue, sans-serif"
    // More host config options
});

// Parse the card payload
adaptiveCard.parse(card);

// Render the card to an HTML element:
var renderedCard = adaptiveCard.render();

// And finally insert it somewhere in your page:
DOM.window.document.getElementById("cardAdaptive").appendChild(renderedCard);

// Print HTML string
console.log(DOM.serialize());

Program output (HTML):

<!DOCTYPE html>
<html>

<head></head>

<body id="cardAdaptive">

    <div class="ac-container ac-adaptiveCard"
        style="display: flex; flex-direction: column; justify-content: flex-start; box-sizing: border-box; flex: 0 0 auto; padding: 15px 15px 15px 15px; margin: 0px 0px 0px 0px;"
        tabindex="0">
        <div
            style="display: flex; align-items: flex-start; justify-content: flex-start; box-sizing: border-box; flex: 0 0 auto;">
            <img style="max-height: 100%; min-width: 0; max-width: 100%;" class="ac-image"
                src="http://adaptivecards.io/content/adaptive-card-50.png"></div>
        <div class="ac-horizontal-separator"
            style="height: 8px; overflow: hidden; margin-right: 0px; margin-left: 0px; flex: 0 0 auto;"></div>
        <div class="ac-textBlock"
            style="overflow: hidden; font-family: Segoe UI, Helvetica Neue, sans-serif; font-size: 14px; color: rgb(51, 51, 51); font-weight: 400; text-align: left; line-height: 18.62px; white-space: nowrap; text-overflow: ellipsis; box-sizing: border-box; flex: 0 0 auto;">
        </div>
        <div class="ac-horizontal-separator" style="height: 8px; overflow: hidden;"></div>
        <div>
            <div style="overflow: hidden;">
                <div class="ac-actionSet" style="display: flex; flex-direction: row; justify-content: flex-start;">
                    <button aria-label="Learn more" type="button"
                        style="display: flex; align-items: center; justify-content: center; flex: 0 1 auto;" role="link"
                        class="ac-pushButton style-default">
                        <div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"></div>
                    </button>
                    <div style="flex: 0 0 auto; width: 20px;"></div><button aria-label="GitHub" type="button"
                        style="display: flex; align-items: center; justify-content: center; flex: 0 1 auto;" role="link"
                        class="ac-pushButton style-default">
                        <div style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"></div>
                    </button>
                </div>
            </div>
            <div></div>
        </div>
    </div>
</body>

</html>

Parsed HTML looked like this:

  • Text fields and contents are missing.

enter image description here

But expected something like this:

enter image description here

Update

I tried to set the hostConfig as mentioned in this stackoverflow answer and all the sample host configs mentioned in this github. None of them worked. It looks like the fields having texts are always missing. Now I've no idea how to make this working.

My specs for this program:

  • Ubuntu 18.04.4 LTS
  • Nodejs: v12.18.1
  • "adaptivecards": "^1.2.6",
  • "jsdom": "^16.2.2",

Reference Links:



Solution 1:[1]

Adaptive card library render text as innerText of HtmlElement. innerText is not supported by jsdom, that's why text were all gone.

You can override the default TextBox rendering like this:

class CustomTextBox extends AdaptiveCards.TextBlock{
  overrideInternalRender() {
  var element = super.overrideInternalRender();
  element.textContent = element.innerText;

  return element;
  }
}

AdaptiveCards.GlobalRegistry.elements.register("TextBlock", CustomTextBox);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tyler2P