Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(TU-13163): Add the duplicate-detected callback #660

Merged
merged 4 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions docs/callbacks.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ Available callbacks:
- **onQuestionChanged** fires when user navigates between form questions
- **onHeightChanged** fires when height of currently displayed question changes
- **onEndingButtonClick** fires when user clicks on the button on your typeform ending screen (it also disables the redirect functionality)
- **onDuplicateDetected** fires when the respondent reaches the quota of responses defined in [the duplicate prevention setting](https://www.typeform.com/help/a/prevent-duplicate-responses-27917825492244/)

Each callback receives a payload object with `formId` to identify the typeform that sent the event.
Depending on the callback there might be more data in the payload - see examples below.
Expand Down Expand Up @@ -246,6 +247,37 @@ Or in HTML:
</script>
```

## onDuplicateDetected

The `onDuplicateDetected` callback will execute whenever we detect the respondent reached the quota of responses
defined in [the duplicate prevention setting](https://www.typeform.com/help/a/prevent-duplicate-responses-27917825492244/).

In JavaScript:

```javascript
import { createSlider } from '@typeform/embed'
import '@typeform/embed/build/css/slider.css'

createSlider('<form-id>', {
onDuplicateDetected: ({ formId }) => {
console.log(`Duplicate detected for form ${formId}`)
},
})
```

Or in HTML:

```html
<button data-tf-slider="<form-id>" data-tf-on-duplicate-detected="duplicateDetected">open</button>
<script src="//embed.typeform.com/next/embed.js"></script>
<script>
// this function needs to be available on global scope (window)
function duplicateDetected({ formId }) {
console.log(`Duplicate detected for form ${formId}`)
}
</script>
```

## What's next?

Learn more about [contributing](/embed/contribute), or see what other open-source developers have created on the [Community projects](/community/) page.
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ If you embed via HTML, you need to pass optinos as attributes with `data-tf-` pr
| onQuestionChanged | function | fires when user navigates between form questions | `undefined` |
| onHeightChanged | function | fires when form question height changes (eg. on navigation between questions or on error message) | `undefined` |
| onEndingButtonClick | function | fires when button on ending screen is clicked | `undefined` |
| onDuplicateDetected | function | fires when the respondent reaches the quota of responses defined in [the duplicate prevention setting](https://www.typeform.com/help/a/prevent-duplicate-responses-27917825492244/) | `undefined` |
| autoResize | string / boolean | resize form to always fit the displayed question height, avoid scrollbars in the form (inline widget only), set min and max height separated by coma, eg. `"200,600"` | `false` |
| shareGaInstance | string / boolean | shares Google Analytics instance of the host page with embedded typeform, you can provide your Google Analytics ID to specify which instance to share (if you have more than one in your page) | `false` |
| inlineOnMobile | boolean | removes placeholder welcome screen in mobile and makes form show inline instead of fullscreen | `false` |
Expand Down
42 changes: 42 additions & 0 deletions packages/demo-html/public/callbacks-duplicate-prevention.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Static HTML Demo</title>
<style>
#wrapper {
width: 100%;
max-width: 600px;
height: 400px;
margin: 0 auto;
}
.element {
position: fixed;
bottom: 0;
right: 0;
background: red;
color: white;
padding: 10px;
width: 200px;
height: 60px;
line-height: 40px;
z-index: 10000;
}
</style>
<link rel="stylesheet" href="./lib/css/widget.css" />
</head>
<body>
<div class="element">I have z-index 10k</div>
<div id="wrapper"></div>
<script src="./lib/embed.js"></script>
<script>
window.tf.createWidget('pfnUIIso', {
container: document.getElementById('wrapper'),
onDuplicateDetected: ({formId}) => {
console.log(`Form ${formId} is already answered`)
},
})
</script>
</body>
</html>
6 changes: 6 additions & 0 deletions packages/embed/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ Closing and opening a typeform in modal window will restart the progress from th
| [onQuestionChanged](https://codesandbox.io/s/github/Typeform/embed-demo/tree/main/demo-html/callbacks) | function | fires when user navigates between form questions | `undefined` |
| [onHeightChanged](https://codesandbox.io/s/github/Typeform/embed-demo/tree/main/demo-html/callbacks) | function | fires when form question height changes (eg. on navigation between questions or on error message) | `undefined` |
| [onEndingButtonClick](https://codesandbox.io/s/github/Typeform/embed-demo/tree/main/demo-html/callbacks) | function | fires when button on ending screen is clicked, disables button redirect functionality | `undefined` |
| onDuplicateDetected | function | fires when the respondent reaches the quota of responses defined in [the duplicate prevention setting](https://www.typeform.com/help/a/prevent-duplicate-responses-27917825492244/) | `undefined` |
| [autoResize](https://codesandbox.io/s/github/Typeform/embed-demo/tree/main/demo-html/widget-autoresize) | string / boolean | resize form to always fit the displayed question height, avoid scrollbars in the form (inline widget only), set min and max height separated by coma, eg. `"200,600"` | `false` |
| [shareGaInstance](https://codesandbox.io/s/github/Typeform/embed-demo/tree/main/demo-html/widget-inline) | string / boolean | shares Google Analytics instance of the host page with embedded typeform, you can provide your Google Analytics ID to specify which instance to share (if you have more than one in your page) | `false` |
| [inlineOnMobile](https://codesandbox.io/s/github/Typeform/embed-demo/tree/main/demo-html/widget-inline) | boolean | removes placeholder welcome screen in mobile and makes form show inline instead of fullscreen | `false` |
Expand Down Expand Up @@ -255,6 +256,9 @@ You can listen to form events by providing callback methods:

// for plans with "Redirect from ending screen" feature you also receive `ref`:
console.log(`Ending button clicked in end screen ${ref}`)
},
onDuplicateDetected: ({ formId }) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding an example here 👍

Please add one to the packages/demo-html/public/callbacks.html as well, so we have a runnable example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mathio Do you happen to have access to the test form? We'd need to enable the duplicate prevention feature there.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont think I do :( Actually, it might be better to create a new form and new HTML page for demo purposes, since this feature would affect other demos when enabled.

console.log(`User reached the quote of responses for form ${formId}`)
}
})
document.querySelector('#btn').click = () => {
Expand Down Expand Up @@ -285,6 +289,8 @@ Callback method receive payload object from the form. Each payload contains form
- onEndingButtonClick
- `formId` (string)
- `ref` (string) identifies the end screen (_Note:_ this is available for plans with "Redirect from ending screen" feature only.)
- onDuplicateDetected
- `formId` (string)

See [callbacks example in demo package](../../packages/demo-html/public/callbacks.html).

Expand Down
7 changes: 7 additions & 0 deletions packages/embed/src/base/actionable-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,11 @@ export type ActionableOptions = {
* @param {string} event.ref - End screen ref string (for plans with "Redirect from ending screen" feature).
*/
onEndingButtonClick?: (event: WithFormId & Partial<WithRef>) => void

/**
* Callback function that will be executed once we detect the current user reached the form answer quota.
* @param {Object} event - Event payload.
* @param {string} event.formId - Form ID string.
*/
onDuplicateDetected?: (event: WithFormId) => void
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe('build-options-from-attributes', () => {
data-tf-on-submit="onTypeformSubmit"
data-tf-on-question-changed="onTypeformQuestionChanged"
data-tf-on-height-changed="onTypeformHeightChanged"
data-tf-on-duplicate-detected="onDuplicateDetected"
data-tf-auto-resize="100,300"
data-tf-open="exit"
data-tf-open-value="3000"
Expand Down Expand Up @@ -45,6 +46,7 @@ describe('build-options-from-attributes', () => {
win.onTypeformSubmit = jest.fn()
win.onTypeformQuestionChanged = jest.fn()
win.onTypeformHeightChanged = jest.fn()
win.onDuplicateDetected = jest.fn()

const element = wrapper.querySelector('#element') as HTMLElement
const options = buildOptionsFromAttributes(element)
Expand All @@ -62,6 +64,7 @@ describe('build-options-from-attributes', () => {
onSubmit: win.onTypeformSubmit,
onQuestionChanged: win.onTypeformQuestionChanged,
onHeightChanged: win.onTypeformHeightChanged,
onDuplicateDetected: win.onDuplicateDetected,
autoResize: '100,300',
open: 'exit',
openValue: 3000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const buildOptionsFromAttributes = (element: HTMLElement) => {
onSubmit: 'function',
onQuestionChanged: 'function',
onHeightChanged: 'function',
onDuplicateDetected: 'function',
autoResize: 'stringOrBoolean',
onClose: 'function',
onEndingButtonClick: 'function',
Expand Down
3 changes: 3 additions & 0 deletions packages/embed/src/utils/create-iframe/create-iframe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
getThankYouScreenButtonClickHandler,
getFormStartedHandler,
getRedirectHandler,
getDuplicateDetectedHandler,
} from './get-form-event-handler'
import { triggerIframeRedraw } from './trigger-iframe-redraw'
import { dispatchCustomKeyEventFromIframe } from './setup-custom-keyboard-close'
Expand All @@ -35,6 +36,7 @@ export const createIframe = (type: EmbedType, { formId, domain, options }: Creat
onHeightChanged,
onSubmit,
onEndingButtonClick,
onDuplicateDetected,
shareGaInstance,
} = options

Expand Down Expand Up @@ -67,6 +69,7 @@ export const createIframe = (type: EmbedType, { formId, domain, options }: Creat
window.addEventListener('message', getFormThemeHandler(embedId, onTheme))
window.addEventListener('message', getThankYouScreenButtonClickHandler(embedId, onEndingButtonClick))
window.addEventListener('message', getRedirectHandler(embedId, iframe))
window.addEventListener('message', getDuplicateDetectedHandler(embedId, onDuplicateDetected))

if (type !== 'widget') {
window.addEventListener('message', dispatchCustomKeyEventFromIframe)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ export const getFormSubmitHandler = (embedId: string, callback?: callbackFn) =>
return getFormEventHandler('form-submit', embedId, callback)
}

export const getDuplicateDetectedHandler = (embedId: string, callback?: callbackFn) => {
return getFormEventHandler('duplicate-detected', embedId, callback)
}

export const getWelcomeScreenHiddenHandler = (embedId: string, callback?: callbackFn) => {
return getFormEventHandler('welcome-screen-hidden', embedId, callback)
}
Expand Down
Loading