1

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:

6
  • Hey Saurav, I've seen similar before. If i remember right it was sort of related to the host config. You have to set values in some cases. Especially when there is no parent styles that can be inherited from other parts of your page. Its definitely working like that. Did the same before in a smaller project. If host config doesnt fix your issue i dig out my old code Commented Jun 19, 2020 at 7:52
  • @TimCadenbach: Thanks for your quick response. Can you please point out how to give a correct host config value. Also I tried to mock the same functionality in browser by adding <script src="unpkg.com/adaptivecards/dist/adaptivecards.js"> in my html file and it was parsed without any problem. Commented Jun 19, 2020 at 7:59
  • Yea thats what i thought. The host config takes a few default values from your browser. If you're not running in a browser, don't have window etc etc this fails. You pretty much only have to set all the important values in your host config. stackoverflow.com/questions/44049239/… This question has a pretty detailed host config in one of the answers. Just take this and tweak it to your needs. Commented Jun 19, 2020 at 8:15
  • Its not completely perfect at this stage and you might need to do a bit of work to have it work for you but maybe this can help you aswell: github.com/deejaytc/adaptivecards-wc Its pretty much a card renderer library you can use in plain html or js contents Commented Jun 19, 2020 at 8:17
  • @TimCadenbach: I tried the hostConfig as mentioned in 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. I've no idea now how to make this working. I've not tried adaptivecards-wc as of now not sure whether it can be used as a backend parser which fulfils my requirements. Commented Jun 19, 2020 at 10:16

1 Answer 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);
0

Not the answer you're looking for? Browse other questions tagged or ask your own question.