Skip to content

Commit d769574

Browse files
OEvgenycompulim
andauthored
feat: add activity related fields into the form data (#5418)
* feat: add acitivty related fields into the form data * Polish * fix space * Changelog * Clarify changelog entry Co-authored-by: William Wong <[email protected]> --------- Co-authored-by: William Wong <[email protected]>
1 parent ab274ed commit d769574

File tree

7 files changed

+142
-2
lines changed

7 files changed

+142
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,6 +2037,7 @@ It should check the result from downstream middleware. If it is falsy, it should
20372037
- Adds Direct Line Speech support, by [@compulim](https://github.com/compulim) in PR [#2621](https://github.com/microsoft/BotFramework-WebChat/pull/2621)
20382038
- Adds [`[email protected]`](https://npmjs.com/package/microsoft-cognitiveservices-speech-sdk), in PR [#2704](https://github.com/microsoft/BotFramework-WebChat/pull/2704)
20392039
- Fixes [#2692](https://github.com/microsoft/BotFramework-WebChat/issues/2692). Rename sample 17 to 17.a, by [@corinagum](https://github.com/corinagum) in PR [#2695](https://github.com/microsoft/BotFramework-WebChat/pull/2695)
2040+
- Added support for including activity ID and key into form data indicated by `data-webchat-include-activity-id` and `data-webchat-include-activity-key` attributes, in PR [#5418](https://github.com/microsoft/BotFramework-WebChat/pull/5418), by [@OEvgeny](https://github.com/OEvgeny)
20402041

20412042
### Fixed
20422043

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<!doctype html>
2+
<html lang="en-US">
3+
4+
<head>
5+
<link href="/assets/index.css" rel="stylesheet" type="text/css" />
6+
<script crossorigin="anonymous" src="/test-harness.js"></script>
7+
<script crossorigin="anonymous" src="/test-page-object.js"></script>
8+
<script crossorigin="anonymous" src="/__dist__/webchat-es5.js"></script>
9+
<style>
10+
form {
11+
display: flex;
12+
flex-flow: column nowrap;
13+
gap: 4px;
14+
}
15+
16+
form output {
17+
white-space: pre-wrap;
18+
padding: 4px;
19+
}
20+
</style>
21+
</head>
22+
23+
<body>
24+
<template id="form">
25+
<form
26+
data-webchat-include-activity-id="activityId"
27+
data-webchat-include-activity-key="activityKey"
28+
>
29+
<button>Send form</button>
30+
<output></output>
31+
</form>
32+
</template>
33+
<main onsubmit="handleSubmit(event)" id="webchat"></main>
34+
<script>
35+
function handleSubmit(event) {
36+
const form = event.target;
37+
event.preventDefault();
38+
const data = Object.fromEntries(new FormData(form).entries());
39+
form.elements[1].value = `${'activityKey' in data} ${data.activityId}`;
40+
}
41+
42+
run(async function () {
43+
const {
44+
WebChat: { renderWebChat }
45+
} = window; // Imports in UMD fashion.
46+
47+
const allowFormElementsTransform = () => next => request =>
48+
next({
49+
...request,
50+
allowedTags: Object.freeze(
51+
new Map(request.allowedTags)
52+
.set('form', Object.freeze({ attributes: Object.freeze(['data-webchat-include-activity-id', 'data-webchat-include-activity-key']) }))
53+
.set('button', Object.freeze({ attributes: Object.freeze([]) }))
54+
.set('output', Object.freeze({ attributes: Object.freeze([]) }))
55+
)
56+
});
57+
58+
const { directLine, store } = testHelpers.createDirectLineEmulator();
59+
60+
renderWebChat(
61+
{ directLine, htmlContentTransformMiddleware: [allowFormElementsTransform], store },
62+
document.getElementById('webchat')
63+
);
64+
65+
await pageConditions.uiConnected();
66+
67+
await directLine.emulateIncomingActivity({
68+
id: 'my-activity-id',
69+
text: window.form.innerHTML,
70+
type: 'message'
71+
});
72+
73+
74+
await pageConditions.numActivitiesShown(1);
75+
76+
pageElements.sendBoxTextBox().focus();
77+
78+
// WHEN: Clicking on Send Form button
79+
await host.sendShiftTab(2);
80+
await host.sendKeys('ARROW_UP');
81+
await host.sendKeys('ENTER');
82+
await host.sendKeys('ENTER');
83+
84+
// THEN: The form with activity related fields is shown
85+
await host.snapshot('local');
86+
87+
// WHEN: Stolen form is submitted
88+
const form = document.querySelector('article form').cloneNode(true)
89+
window.webchat.prepend(form);
90+
form.requestSubmit();
91+
92+
// THEN: No activity related fields are present
93+
await host.snapshot('local');
94+
});
95+
</script>
96+
</body>
97+
98+
</html>
10.8 KB
Loading
10.5 KB
Loading

packages/component/src/Transcript/ActivityRow.tsx

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const { useActivityKeysByRead, useGetHasAcknowledgedByActivityKey, useGetKeyByAc
2020

2121
type ActivityRowProps = PropsWithChildren<{ activity: WebChatActivity }>;
2222

23-
const ActivityRow = forwardRef<HTMLLIElement, ActivityRowProps>(({ activity, children }, ref) => {
23+
const ActivityRow = forwardRef<HTMLElement, ActivityRowProps>(({ activity, children }, ref) => {
2424
const [activeDescendantId] = useActiveDescendantId();
2525
const [readActivityKeys] = useActivityKeysByRead();
2626
const bodyRef = useRef<HTMLDivElement>();
@@ -65,6 +65,47 @@ const ActivityRow = forwardRef<HTMLLIElement, ActivityRowProps>(({ activity, chi
6565
[bodyRef, children]
6666
);
6767

68+
const activityIdRef = useRefFrom(activity.id);
69+
70+
const handleFormData = useCallback(
71+
(event: FormDataEvent & { target: HTMLFormElement }) => {
72+
const { webchatIncludeActivityId, webchatIncludeActivityKey } = event.target.dataset;
73+
if (webchatIncludeActivityId) {
74+
event.formData.set(webchatIncludeActivityId, activityIdRef.current ?? '');
75+
}
76+
if (webchatIncludeActivityKey) {
77+
event.formData.set(webchatIncludeActivityKey, activityKeyRef.current);
78+
}
79+
},
80+
81+
[activityKeyRef, activityIdRef]
82+
);
83+
84+
const prevArticleRef = useRef<HTMLElement>(null);
85+
86+
const wrappedRef = useCallback(
87+
(el: HTMLElement | null) => {
88+
if (prevArticleRef.current) {
89+
prevArticleRef.current.removeEventListener('formdata', handleFormData);
90+
}
91+
92+
if (el) {
93+
el.addEventListener('formdata', handleFormData);
94+
}
95+
96+
prevArticleRef.current = el;
97+
98+
if (ref) {
99+
if (typeof ref === 'function') {
100+
ref(el);
101+
} else {
102+
ref.current = el;
103+
}
104+
}
105+
},
106+
[handleFormData, ref]
107+
);
108+
68109
return (
69110
// TODO: [P2] Add `aria-roledescription="message"` for better AX, need localization strings.
70111
<article
@@ -74,7 +115,7 @@ const ActivityRow = forwardRef<HTMLLIElement, ActivityRowProps>(({ activity, chi
74115
})}
75116
// When NVDA is in browse mode, using up/down arrow key to "browse" will dispatch "click" and "mousedown" events for <article> element (inside <LiveRegionActivity>).
76117
onMouseDownCapture={handleMouseDownCapture}
77-
ref={ref}
118+
ref={wrappedRef}
78119
>
79120
{/* TODO: [P1] File a crbug for TalkBack. It should not able to read the content twice when scanning. */}
80121

0 commit comments

Comments
 (0)