Search code examples
typescriptbotframeworkweb-chat

BotFramework-Webchat Middleware: set css class names


I'd like to add css class names depending on the card type to the corresponding html element.

I already did this by setting the [AdaptiveCard.customCssSelector] 1 attribute. E.g. you just need to add builder.card.customCssSelector = "ac-thumbnail"; in this line and the resulting html-block will contain class="ac-container ac-thumbnail".

However, I'd like to be independent of Botframework-Webchat updates and put this logic into a middleware. According to the BotFramework-Webchat docs it's possible to add attachmentMiddleware to the renderWebChat function and manipulate the html elements.
In fact I get activities and attachments, but I'm not able to manipulate html-blocks or add a css selector.

Here's my middleware code:

export const cardCssSelector = ({ dispatch }) => next => action => {
    if (action.type === 'DIRECT_LINE/INCOMING_ACTIVITY') {
        const { activity } = action.payload;
         for (var index in activity.attachments) {       
            const card  = activity.attachments[index].content;
            if(card.tap && card.tap.value){
                card.customCssSelector = 'tap';
            }
        }
    }
    console.log(action);
    return next(action);
};

Of course this doesn't work, since the attachments don't have the customCssSelector attribute.


Solution

  • To do true custom styling of web chat, then a little hacking is required. Some things to note:

    • You need to match to a value that is passed in the card, not in the activity. This allows you to identify the specific card(s) in the html document to be styled. I'm matching on the button text value, for simplicity.
    • In Web Chat, with respect to the document, cards are rendered as adaptive cards.
    • I recast the adaptiveCards [HTMLCollection] to a true array (cards) for iterating.
    • I add the card- classes for mapping the CSS to the card(s).
    • Because a basic adaptive card has the same document structure as a converted "hero card" adaptive card, I can count on the buttons being the 3rd child (children[2]) for retrieving values. You will need to account for variations in your cards.
    • For activities with multiple cards, you will likely need to make adjustments but should be doable following a similar setup to the below.

    First, create a store and filter on incoming activities, messages, and then on the ac-adaptiveCard class.

    const store = window.WebChat.createStore( {}, ( { dispatch } ) => next => async action => {
      if(activity.type === 'message') {
        let aCards = document.body.getElementsByClassName('ac-adaptiveCard');
        let bCards = Object.values( aCards);
    
        for(let i = 0; i <= bCards.length - 1; i++) {
          if( cards[i].children[2] && cards[i].children[2].innerText && cards[ i ].children[ 2 ].innerText === 'Request Assistance') {
            cards[i].className += ' card-Adaptive'
          }
    
          if( cards[i].children[2] && cards[i].children[2].innerText && cards[ i ].children[ 2 ].innerText === 'Service details') {
            cards[i].className += ' card-Hero'
          }
        }
      }
    }
    

    I apply styling to the cards by assigned class.

    .card-Adaptive {
      background-color: black;
    }
    
    .card-Adaptive p {
      color: white;
    }
    
    .card-Hero {
      background-color: green;
    }
    

    I pass store as a parameter.

    window.ReactDOM.render(
      <ReactWebChat
        directLine={ directLine }
        store={store}
      />,
      document.getElementById( 'webchat' )
    );
    

    The adaptive card is black and the hero card is green.

    enter image description here