Skip to content

Commit 1a6bc27

Browse files
author
Max Prilutskiy
authored
feat: Added onReady & onClose to the API (#28)
* feat: Added onReady & onClose to the API
1 parent e0d0bab commit 1a6bc27

File tree

12 files changed

+106
-48
lines changed

12 files changed

+106
-48
lines changed

.editorconfig

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,5 @@ end_of_line=lf
99
charset=utf-8
1010
trim_trailing_whitespace=true
1111
insert_final_newline=true
12-
13-
[*.js]
1412
indent_style=space
1513
indent_size=2

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,5 @@ node_modules
5757
# codeceptjs
5858
e2e/output
5959

60-
# cypress
61-
cypress
60+
# cypress
61+
cypress

.travis.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
os: linux
2-
dist: trusty
2+
dist: xenial
33

44
language: node_js
55

@@ -10,6 +10,7 @@ python:
1010
- "3.6"
1111

1212
addons:
13+
# https://stackoverflow.com/questions/57903415/travis-ci-chrome-62-instead-of-77
1314
chrome: stable
1415
firefox: "67.0"
1516

README.md

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ typeformEmbed.makeWidget(element, url, options)
5757
| hideFooter | Hide typeform footer, that appears showing the progress bar and the navigation buttons. | `Boolean` | false |
5858
| hideHeaders | Hide typeform header, that appears when you have a Question group, or a long question that you need to scroll through to answer, like a Multiple Choice block. | `Boolean` | false |
5959
| onSubmit | Callback function that will be executed right after the typeform is successfully submitted. | `Function` | - |
60+
| onReady | Callback function that will be executed once the typeform is ready. | `Function` | - |
6061

6162
#### Example:
6263

@@ -73,6 +74,9 @@ typeformEmbed.makeWidget(element, url, options)
7374
hideScrollbars: true,
7475
onSubmit: function () {
7576
console.log('Typeform successfully submitted')
77+
},
78+
onReady: function () {
79+
console.log('Typeform is ready')
7680
}
7781
}
7882
)
@@ -99,6 +103,10 @@ typeformEmbed.makePopup(url, options)
99103
| hideHeaders | Hide typeform header, that appears when you have a Question group, or a long question that you need to scroll through to answer, like a Multiple Choice block. | `Boolean` | false |
100104
| drawerWidth | Specify the width of the drawer (only applies if using `mode` `"drawer_left"` or `"drawer_right"`). | `Number` (pixels) | 800 |
101105
| onSubmit | Callback function that will be executed right after the typeform is successfully submitted. | `Function` | - |
106+
| onReady | Callback function that will be executed once the typeform is ready. |
107+
`Function` | - |
108+
| onClose | Callback function that will be executed once the typeform is closed. |
109+
`Function` | - |
102110

103111
#### Example:
104112

@@ -112,6 +120,12 @@ typeformEmbed.makePopup(url, options)
112120
hideScrollbars: true,
113121
onSubmit: function () {
114122
console.log('Typeform successfully submitted')
123+
},
124+
onReady: function () {
125+
console.log('Typeform is ready')
126+
},
127+
onClose: function () {
128+
console.log('Typeform is closed')
115129
}
116130
}
117131
)
@@ -140,15 +154,15 @@ Although we have no hard limit, we recommend having a height of at least 350px.
140154
We use `position: fixed` to position our modal relative to its containing block established by the viewport. If one of the modal ancestors has a `transform`, `perspective`, or `filter` css property set to something other than `none` the positioning will be relative to it and probably not visible by the user.
141155

142156
## Tests
143-
In order to run visual tests, it is need an applitools key.
144-
- Add a `.env` file in your root, you can look at the `.env.example`
157+
In order to run visual tests, you need an applitools key.
158+
- Add a `.env` file in your root, you can look at the `.env.example`
145159
- Add your api key `EYES_API_KEY=HERE_GOES_YOUR_KEY`
146160
- (Optional) You can add the url, by default the url is `http://localhost:8080`
147161
- Start the server `yarn start`
148162
- Run the tests with `yarn test`
149163
- - This command will run all the tests. That means **unit tests**, **integration tests** and **visual tests**
150164

151-
This is the list of all the test commands, if you want to run them one by one:
165+
This is the list of all the test commands, if you want to run them one by one:
152166
- `yarn test:unit` --> Runs unit tests
153167
- `yarn test:functional` --> Runs cross browser functional tests with Cypress
154168
- `yarn test:visual` --> Runs visual tests with CodeceptJs, WebDriver, and Applitools.

package.json

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,29 +21,29 @@
2121
"clean:lib": "rm -rf lib",
2222
"start": "NODE_ENV=development webpack-dev-server -d --config webpack.config.dist.js",
2323
"test": "yarn lint && yarn test:unit && yarn test:visual && yarn test:functional",
24-
"lint": "eslint ./src --ext .js --ignore-path .eslintignore",
25-
"test:unit": "jest",
26-
"test:unit:watch": "jest --watch",
27-
"test:unit:coverage": "jest --coverage",
28-
"test:visual": "yarn test:visual:install && yarn test:visual:chrome && yarn test:visual:firefox && yarn test:visual:mobile",
24+
"lint": "yarn eslint ./src --ext .js --ignore-path .eslintignore",
25+
"test:unit": "yarn jest",
26+
"test:unit:watch": "yarn jest --watch",
27+
"test:unit:coverage": "yarn jest --coverage",
28+
"test:visual": "yarn run test:visual:install && yarn run test:visual:chrome && yarn run test:visual:firefox && yarn run test:visual:mobile",
2929
"test:visual:install": "yarn selenium-standalone install --silent",
3030
"test:visual:chrome": "yarn codeceptjs --config codecept-visual.conf.js run --steps --grep @desktop",
3131
"test:visual:firefox": "yarn codeceptjs --config codecept-visual.conf.js run --steps --profile firefox --grep @desktop",
3232
"test:visual:mobile": "yarn codeceptjs --config codecept-visual.conf.js run --steps --grep @mobile",
3333
"test:functional:debug": "yarn cypress open",
34-
"test:functional": "yarn test:functional:chrome && yarn test:functional:firefox",
34+
"test:functional": "yarn run test:functional:chrome && yarn run test:functional:firefox",
3535
"test:functional:firefox": "yarn cypress run --browser firefox",
3636
"test:functional:chrome": "yarn cypress run --browser chrome",
3737
"prepublish": "yarn run lib",
38-
"semantic-release": "semantic-release --branch release",
39-
"travis-deploy-once": "travis-deploy-once --pro",
38+
"semantic-release": "yarn semantic-release --branch release",
39+
"travis-deploy-once": "yarn travis-deploy-once --pro",
4040
"copy": "yarn run copy:assets && yarn run copy:helpcenter && yarn run copy:demo",
41-
"copy:assets": "copyfiles -f assets/* dist",
42-
"copy:helpcenter": "copyfiles -f helpcenter/* dist",
43-
"copy:demo": "copyfiles -f demo/* dist",
41+
"copy:assets": "yarn copyfiles -f assets/* dist",
42+
"copy:helpcenter": "yarn copyfiles -f helpcenter/* dist",
43+
"copy:demo": "yarn copyfiles -f demo/* dist",
4444
"build": "yarn run dist && yarn run lib && yarn run copy",
45-
"dist": "yarn run clean:dist && webpack --config webpack.config.dist.js",
46-
"lib": "yarn run clean:lib && webpack --config webpack.config.lib.js"
45+
"dist": "yarn run clean:dist && yarn webpack --config webpack.config.dist.js",
46+
"lib": "yarn run clean:lib && yarn webpack --config webpack.config.lib.js"
4747
},
4848
"devDependencies": {
4949
"@applitools/eyes-webdriverio": "^5.7.2",
@@ -72,6 +72,7 @@
7272
"jsdom": "^11.6.2",
7373
"react": "16.12.0",
7474
"react-dom": "16.12.0",
75+
"regenerator-runtime": "^0.13.5",
7576
"selenium-standalone": "^6.17.0",
7677
"semantic-release": "^12.2.5",
7778
"travis-deploy-once": "^4.3.3",

src/core/make-popup.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Popup, {
1818
DEFAULT_AUTOCLOSE_TIMEOUT
1919
} from './views/popup'
2020
import MobileModal from './views/mobile-modal'
21+
import { getPostMessageHandler } from './utils/get-post-message-handler'
2122

2223
const DEFAULT_DRAWER_WIDTH = 800
2324

@@ -41,7 +42,8 @@ const queryStringKeys = {
4142
disableTracking: 'disable-tracking'
4243
}
4344

44-
const renderComponent = (url, domNode, options, onClose) => {
45+
const renderComponent = (params, options) => {
46+
const { url, domNode, close } = params
4547
const {
4648
autoClose,
4749
buttonText,
@@ -59,7 +61,7 @@ const renderComponent = (url, domNode, options, onClose) => {
5961
render(
6062
<Popup
6163
embedId={embedId}
62-
onClose={onClose}
64+
onClose={close}
6365
options={options}
6466
url={urlWithQueryString}
6567
/>,
@@ -73,7 +75,7 @@ const renderComponent = (url, domNode, options, onClose) => {
7375
buttonText={buttonText}
7476
embedId={embedId}
7577
isAutoCloseEnabled={isAutoCloseEnabled}
76-
onClose={onClose}
78+
onClose={close}
7779
onSubmit={onSubmit}
7880
open
7981
url={urlWithQueryString}
@@ -84,6 +86,9 @@ const renderComponent = (url, domNode, options, onClose) => {
8486
}
8587

8688
export default function makePopup (url, options) {
89+
window.addEventListener('message', getPostMessageHandler('form-ready', options.onReady))
90+
window.addEventListener('message', getPostMessageHandler('form-closed', options.onClose))
91+
8792
const embedId = randomString()
8893

8994
options = {
@@ -109,10 +114,17 @@ export default function makePopup (url, options) {
109114
open (event) {
110115
const { currentTarget } = event || {}
111116
const currentUrl = currentTarget && currentTarget.href ? currentTarget.href : url
112-
renderComponent(currentUrl, domNode, options, this.close)
117+
const params = {
118+
domNode,
119+
url: currentUrl,
120+
close: this.close
121+
}
122+
123+
renderComponent(params, options)
113124
},
114125
close () {
115126
window.postMessage({ type: 'form-closed', embedId }, '*')
127+
116128
unmountComponentAtNode(domNode)
117129
}
118130
}

src/core/make-popup.spec.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,27 @@ describe('makePopup', () => {
104104
expect(component.type.name).toEqual('Popup')
105105
expect(component.props.options).toEqual(expect.objectContaining(options))
106106
})
107+
108+
it(`onReady is called during initialization`, async () => {
109+
const options = { onReady: jest.fn() }
110+
111+
makePopup(URL, options)
112+
113+
window.postMessage({ type: 'form-ready' }, '*')
114+
await new Promise((resolve) => setTimeout(resolve))
115+
expect(options.onReady).toHaveBeenCalledTimes(1)
116+
expect(options.onReady).toHaveBeenCalledWith()
117+
})
118+
119+
it(`onClose is called when form closes`, async () => {
120+
const options = { onClose: jest.fn() }
121+
122+
makePopup(URL, options)
123+
124+
window.postMessage({ type: 'form-closed' }, '*')
125+
await new Promise((resolve) => setTimeout(resolve))
126+
127+
expect(options.onClose).toHaveBeenCalledTimes(1)
128+
expect(options.onClose).toHaveBeenCalledWith()
129+
})
107130
})

src/core/make-widget.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
isMobile
1212
} from './utils/mobile-detection'
1313
import Widget from './views/widget'
14+
import { getPostMessageHandler } from './utils/get-post-message-handler'
1415

1516
const defaultOptions = {
1617
mode: 'embed-widget',
@@ -32,6 +33,8 @@ const queryStringKeys = {
3233
export default function makeWidget (element, url, options) {
3334
options = { ...defaultOptions, ...options }
3435

36+
window.addEventListener('message', getPostMessageHandler('form-ready', options.onReady))
37+
3538
const enabledFullscreen = isMobile(navigator.userAgent)
3639

3740
let queryStrings = replaceExistingKeys(options, queryStringKeys)

src/core/make-widget.spec.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,16 @@ describe('makeWidget', () => {
5858

5959
expect(component.type.name).toEqual('Widget')
6060
})
61+
62+
it(`onReady is called during initialization`, async () => {
63+
const element = document.createElement('div')
64+
const options = { onReady: jest.fn() }
65+
66+
makeWidget(element, URL, options)
67+
68+
window.postMessage({ type: 'form-ready' }, '*')
69+
await new Promise((resolve) => setTimeout(resolve))
70+
expect(options.onReady).toHaveBeenCalledTimes(1)
71+
expect(options.onReady).toHaveBeenCalledWith()
72+
})
6173
})
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export const getPostMessageHandler = (type, handler, options = {}) => (event) => {
2+
try {
3+
if (event.data.type !== type) { return }
4+
5+
if (options.includePayload) {
6+
handler(event)
7+
} else {
8+
handler()
9+
}
10+
} catch (e) { }
11+
}

0 commit comments

Comments
 (0)