BotFramework-WebChat icon indicating copy to clipboard operation
BotFramework-WebChat copied to clipboard

Sample: Disable Adaptive Cards after submit/obsoleted

Open compulim opened this issue 7 years ago โ€ข 39 comments

Goals

  • Write a new attachment renderer that will disable Adaptive Card interactivity if a certain condition is met
    • If the Adaptive Card is no longer the last visible activity sent from the bot, or there is a message sent by the user succeeding it, disable interactivity on the Adaptive Card
  • There could be more than one Adaptive Card form available on the page
  • Also consider, if "expand card" action button should be disabled or not. It was because "expand card" button should not be counted as "form submission", we might not want to disable expand/collapse feature
    • If implementer is not familiar with this feature, should consult the "Adaptive Card visualizer" page for details

Non-goals

It should not use jQuery or any other DOM-manipulating libraries to achieve the goal. It must use pure React.

Reference

We have a sample named "presentation mode". It showed the ability to disable interactivity of the whole Web Chat UI, including Adaptive Card content. The implementer can look into that to understand how to disable interactivity for just a specific attachment

compulim avatar Dec 03 '18 18:12 compulim

will it also include disabling cards and buttons in it after submitting? Cos now its not very obvios after button click that it was actually cliked and if clicked twice by default we have 2 events and messages dublicated. There's a backend workaround for that: https://stackoverflow.com/questions/51701003/microsoft-bot-framework-webchat-disable-adaptivecards-submit-buttons-of-previou , but it seems very hacky. Thanks

Unders0n avatar Feb 24 '19 19:02 Unders0n

Is there any update on this? @compulim @Unders0n I am using adaptive cards inside waterfall dialog exactly like prompt choices, Once the button is clicked it has to be disabled or made obsolete. If there is an update please tell me how do I do it in node js, because the above C# solution is not working for me.

Naveenbc avatar Apr 12 '19 11:04 Naveenbc

Any updated solution for disabling a adaptive card button totally once its clicked once ?

vishmonne avatar May 20 '19 11:05 vishmonne

Any updates ?

NaveenGaneshe avatar May 20 '19 18:05 NaveenGaneshe

The only way I found was to use a custom activityMiddleware where I draw my own message header and actions. As the web-chat component is written in React, I used class state to draw/not draw children (action buttons). This however leads to manual parsing of adaptive cards from card attachments coming into activity middleware.

Documentation: https://github.com/microsoft/BotFramework-WebChat#web-chat-api-reference

activityMiddleware | A chain of middleware, modeled afterย Redux middleware, that allows the developer to add new DOM components on the currently existing DOM of Activities. The middleware signature is the following:ย options => next => card => children => next(card)(children).

avilde avatar May 22 '19 12:05 avilde

Hi, any updates on this feature. We have observed that users always try to scroll up to click on hero card buttons when the response is no longer expected. If would be great if hero card was disabled once it is not longer last activity element.

dkonyayev avatar Nov 06 '19 17:11 dkonyayev

Hi @dkonyayev, we do not have updates at this time. Others who are looking for this feature are welcome to add their +1 to increase traction.

corinagum avatar Nov 06 '19 18:11 corinagum

+1

mmalaiarasan-conga avatar Nov 07 '19 01:11 mmalaiarasan-conga

In our project this was a must have feature. We made a workaround with our Redux store to save last message id (activity.id).In a React component we would check if it is the last message so we do not show the action buttons and those cannot be pressed twice (e.g. authentication).

avilde avatar Nov 07 '19 05:11 avilde

+1 any update on this yet?

sw353552 avatar Jan 06 '20 10:01 sw353552

+1

Jhiertz avatar Jan 13 '20 12:01 Jhiertz

@avilde Can u please show a sample of how u checked if the activity.id is the last message received?

In my case, I will have to only enable the last adaptive card received and disable all the others so that the user can't click on a previously rendered adaptive card and disturb the flow.

Thanks in advance.

sw353552 avatar Jan 20 '20 05:01 sw353552

+1

HesselWellema avatar Feb 07 '20 09:02 HesselWellema

+1

AndrewNagulyak avatar Feb 13 '20 15:02 AndrewNagulyak

+1

MouadhDos avatar Apr 21 '20 21:04 MouadhDos

+1

Vimarech avatar Apr 22 '20 06:04 Vimarech

Will do it this week. ๐Ÿ˜‰

compulim avatar May 04 '20 09:05 compulim

https://tenor.com/uuRX.gif :)

avilde avatar May 04 '20 09:05 avilde

Teaser...

There are still some kinks but reasonable okay as this will be a sample, not production code. I want to spend some more time think about this approach.

recording

Kinks:

  • We will also disable "show card" actions in Adaptive Cards
  • To be fair, we would like to able to disable all legacy cards too, this includes hero card, audio card, video card, etc.
    • Only buttons in all legacy cards should be disabled
    • Animation, audio and video cannot be disabled (HTML also does not have disabled attribute on <audio>/<video> tag)
  • Adaptive Cards
    • When a button is clicked and its value is submitted, the button don't highlight itself. This is by design of Adaptive Cards
    • The sample code hacked the card by setting action.isPrimary to true on submit

One primary goal in this sample, make sure keyboard focus is set correctly after buttons are disabled.

The sample code roughly looks like this and it requires the next version of Web Chat.

const attachmentMiddleware = () => next => ({ activity, attachment, ...others }) => {
  const { activities } = store.getState();
  const messageActivities = activities.filter(activity => activity.type === 'message');
  const recentBotMessage = messageActivities.pop() === activity;

  switch (attachment.contentType) {
    case 'application/vnd.microsoft.card.adaptive':
      return <AdaptiveCardContent content={attachment.content} disabled={!recentBotMessage} />;

    case 'application/vnd.microsoft.card.animation':
      return <AnimationCardContent content={attachment.content} disabled={!recentBotMessage} />;

    case 'application/vnd.microsoft.card.audio':
      return <AudioCardContent content={attachment.content} disabled={!recentBotMessage} />;

    case 'application/vnd.microsoft.card.hero':
      return <HeroCardContent content={attachment.content} disabled={!recentBotMessage} />;

    case 'application/vnd.microsoft.card.oauth':
      return <OAuthCardContent content={attachment.content} disabled={!recentBotMessage} />;

    case 'application/vnd.microsoft.card.receipt':
      return <ReceiptCardContent content={attachment.content} disabled={!recentBotMessage} />;

    case 'application/vnd.microsoft.card.signin':
      return <SignInCardContent content={attachment.content} disabled={!recentBotMessage} />;

    case 'application/vnd.microsoft.card.thumbnail':
      return <ThumbnailCardContent content={attachment.content} disabled={!recentBotMessage} />;

    case 'application/vnd.microsoft.card.video':
      return <VideoCardContent content={attachment.content} disabled={!recentBotMessage} />;

    default:
      return next({ activity, attachment, ...others });
  }
};

const cardActionMiddleware = () => next => ({ cardAction, target, ...others }) => {
  target.isPrimary = true;

  return next({ cardAction, target, ...others });
};

// let styleSet = createStyleSet({ hideSendBox: true });
let styleSet = createStyleSet();

styleSet = {
  ...styleSet,
  adaptiveCardRenderer: {
    ...styleSet.adaptiveCardRenderer,
    '& .ac-pushButton:disabled:not(.primary)': { backgroundColor: '#F7F7F7', color: '#717171' },
    '& .ac-pushButton.primary:disabled': { backgroundColor: '#0078D7', color: 'White' }
  }
};

window.ReactDOM.render(
  <ReactWebChat
    attachmentMiddleware={attachmentMiddleware}
    cardActionMiddleware={cardActionMiddleware}
    directLine={window.WebChat.createDirectLine({ token })}
    store={store}
    styleSet={styleSet}
  />,
  document.getElementById('webchat')
);

compulim avatar May 06 '20 08:05 compulim

Working on the tab order of "New message" button after the AC card is disabled. It's not trivial as we are not allowed to touch tabindex. We can only influence by reordering DOM nodes.

tabindex is global and introduce pollutions. tl;dr we are component, not app, so we don't own the global environment, we must not pollute it. One reason why components are much harder to write than apps.

p.s. love to see my customers are so excited when I am fixing bugs. The little ๐Ÿš€ made my day.

compulim avatar May 07 '20 00:05 compulim

Hi compulim,

thank you very much for your work. I have a question, is it possible to disable only some type of card action? For example, if I want to keep enabled OpenUrl action because it doesn't change the flow.

Thanks a lot, Andrea

BeeMaia avatar May 07 '20 07:05 BeeMaia

@BeeMaia it will be quite difficult to do it today. Will you consider using Markdown to present an hyperlink to the user?

Are you rendering a hero card or Adaptive Cards?

compulim avatar May 07 '20 12:05 compulim

I am still working on the tab order and completed the code to reorder DOM node. Scroll-to-bottom is difficult. ๐Ÿ˜‘

Good flow

recording (2)

Bad flow

Note: Instead of clicking on the "New messages" button, I press TAB to move the focus to the most recent card. So the scroll view move to the bottom.

The "New message" button didn't go away. I am working on this now. Chrome didn't fire scroll event when a new element is added, and the scroll-to-bottom component didn't update its saved scrollHeight value.

recording (3)

compulim avatar May 07 '20 12:05 compulim

@BeeMaia it will be quite difficult to do it today. Will you consider using Markdown to present an hyperlink to the user?

Are you rendering a hero card or Adaptive Cards?

@compulim we are using hero card and adaptive cards.

BeeMaia avatar May 07 '20 14:05 BeeMaia

Still working on focus management, understanding cross browser behavioral differences to see if this sample will work on daily UX.

In the animation below, we will focus on the element but not clicking on them. Yellow indicate what elements has the focus. And we want to see:

  • What element will get focused after the current one is disabled
  • Which one will get focused after pressing TAB

Looks like we should disable only the non-selected buttons, and leave the button clicked continue to be enabled and focusable.

Chrome ๐Ÿ‘๐Ÿป

Chrome did remember the focus on the disabled element. So TAB will go to the next focusable element after the disabled element.

recording (6)

Firefox ๐Ÿ‘Ž๐Ÿป

Firefox did not remember the focus of the disabled element. TAB will go to the scroll view (i.e. the parent container of the disabled element).

Also note that Firefox can TAB to the scroll view if it does not have tabindex="-1". Scroll view in other browsers are not focusable.

recording (10)

Edge UWP ๐Ÿ‘Ž๐Ÿป

Edge UWP is similar to Firefox, did not remember the focus of the disabled element. But TAB will go to first focusable element after document root.

recording (8)

compulim avatar May 08 '20 06:05 compulim

Going to finish the day here. I think we go down the wrong path. In accessibility, disabled button should still be able to TAB to it, shouldn't lose focus.

compulim avatar May 08 '20 10:05 compulim

I would also upvote for @BeeMaia's idea not to disable OpenURL buttons in the adaptive card.

sw353552 avatar May 11 '20 08:05 sw353552

Reopening this as we are still pending for README.md for this (rushy) sample.

Thanks everyone for the input. @beemaia and @sw353552, could you open a new issue on "do not disable openUrl buttons"? And could you share why "links in Markdown" is not sufficient replacement for openUrl buttons? Would love to start a new user story based on your experience.

This sample is unexpectedly challenging because "making an accessible button which can be disabled dynamically" is a topic surprisingly no one visited. IMO, Firefox and Safari did the best job. For details, please read https://github.com/microsoft/BotFramework-WebChat/blob/master/docs/ACCESSIBILITY.md#additional-context. As always, accessibility is our top priority.

If you are interested in bringing this feature to production (i.e. without copying from the sample code), please create a new "feature enhancement" issue and vote. We need data from your feedback to plan our road map. ๐Ÿ˜‰

To smooth out kinks in our UX, please also share reasons why you are not using Suggested Actions but Adaptive Cards for the user to answer questions.

Thanks for everyone's interested in this topic.

compulim avatar Jun 10 '20 19:06 compulim

I understood and got the solution. But can you explain how to do disable previous/obsolte card using Angular9, I'm tring to achive the same thing with Angular9 but getting nowhere.

anandk95 avatar Sep 13 '20 16:09 anandk95