microsoft / BotFramework-WebChat

A highly-customizable web-based client for Azure Bot Services.
https://www.botframework.com/
MIT License
1.56k stars 1.51k forks source link

Sample: Disable Adaptive Cards after submit/obsoleted #1427

Open compulim opened 5 years ago

compulim commented 5 years ago

Goals

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

Unders0n commented 5 years ago

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

Naveenbc commented 5 years ago

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.

vishmonne commented 5 years ago

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

NaveenGaneshe commented 5 years ago

Any updates ?

avilde commented 5 years ago

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).

dkonyayev commented 4 years ago

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.

corinagum commented 4 years ago

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.

mmalaiarasan-conga commented 4 years ago

+1

Naveenbc commented 4 years ago

+1

On Thu, 7 Nov 2019, 6:44 am ManjulaMalaiarasan, notifications@github.com wrote:

+1

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/microsoft/BotFramework-WebChat/issues/1427?email_source=notifications&email_token=ADJISRLIWOGTEOVRXJC74J3QSNTXZA5CNFSM4GH4SBP2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEDIRTMY#issuecomment-550574515, or unsubscribe https://github.com/notifications/unsubscribe-auth/ADJISRJD4IR4UUDP7M5XIADQSNTXZANCNFSM4GH4SBPQ .

avilde commented 4 years ago

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).

sw353552 commented 4 years ago

+1 any update on this yet?

Jhiertz commented 4 years ago

+1

sw353552 commented 4 years ago

@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.

HesselWellema commented 4 years ago

+1

AndrewNagulyak commented 4 years ago

+1

MouadhDos commented 4 years ago

+1

Vimarech commented 4 years ago

+1

compulim commented 4 years ago

Will do it this week. 😉

avilde commented 4 years ago

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

compulim commented 4 years ago

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:

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 commented 4 years ago

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.

BeeMaia commented 4 years ago

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

compulim commented 4 years ago

@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 commented 4 years ago

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)

BeeMaia commented 4 years ago

@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.

compulim commented 4 years ago

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:

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 commented 4 years ago

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.

sw353552 commented 4 years ago

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

compulim commented 4 years ago

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.

anandk95 commented 3 years ago

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.

corinagum commented 3 years ago

Update 05.l (L) sample https://github.com/microsoft/BotFramework-WebChat/tree/master/samples/05.custom-components/l.disable-adaptive-cards

corinagum commented 3 years ago

yes. Bot language does not affect the client behavior. Please use sample provided above.

jayarau commented 3 years ago

Hi @compulim, We are referring above sample link to disable adaptive card. But we are getting disabled card for recent message. and hence not able to fill details. botframework version we are using : 4.11

Screenshot for reference: image

I have tried providing disabled={recentBotMessage} in element but after doing that card is not getting disabled.

<Components.AdaptiveCardContent actionPerformedClassName="card__action--performed"
                content={attachment.content}
                disabled={recentBotMessage}
                />

is anyone facing similar issue? or can help with this.

Kaiqb commented 3 years ago

@jayarau - In the sample, line 115 we have disabled={!recentBotMessage} but in your example you don't have the (!) in front of recentMessage. Could you recheck?

jayarau commented 3 years ago

Hi @Kaiqb, Yes we have tried with disabled={!recentBotMessage} initially, but with that we are getting disabled card even for recent message and hence not able to fill card and submit it. As shown in screenshot above.

jayarau commented 3 years ago

Hi @compulim / @Kaiqb , do we have any update on this?

jayarau commented 2 years ago

Hi @compulim , I see this issue is in dev discussions from long, do we have any update on it?

compulim commented 2 years ago

For everyone who is waiting for this, we will be in planning mode soon. We know this is what our customer want: using AC as an one-off input.

On the other hand, the root cause is in the AC library, https://github.com/microsoft/AdaptiveCards/issues/1512.

Will be great if everyone here could help sending feedbacks to the root cause to get it solved.

jayarau commented 2 years ago

Hi @compulim, We are referring above sample link to disable adaptive card. But we are getting disabled card for recent message. and hence not able to fill details. botframework version we are using : 4.11

Screenshot for reference: image

I have tried providing disabled={recentBotMessage} in element but after doing that card is not getting disabled.

<Components.AdaptiveCardContent actionPerformedClassName="card__action--performed"
               content={attachment.content}
               disabled={recentBotMessage}
               />

is anyone facing similar issue? or can help with this.

Update on this: I am able to disable adaptive cards once action taken. Solution: function check(act){return (act.type === 'message'&&act.hasOwnProperty('attachments'))}; var { activities } = this.store.getState(); var messageActivities=activities.filter(check) var recentBotMessage = messageActivities.pop() === activity;

return (
<Components.AdaptiveCardContent actionPerformedClassName="card__action--performed" content={attachment.content} disabled={!recentBotMessage}/>
);

cobbie commented 1 year ago

+1. How could this be implemented via Bot framework composer?

smelgar-sage commented 1 year ago

+1. How could this be implemented via Bot framework composer?

Any updates about this?