Skip to content

Commit 6ed5b0b

Browse files
committed
Clean up post message logic; add ability to pass data into component.
1 parent f7e2078 commit 6ed5b0b

File tree

4 files changed

+137
-123
lines changed

4 files changed

+137
-123
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ $ npm install react-retool --save
1212
$ yarn add react-retool
1313
```
1414

15-
1615
## Usage
1716

1817
```
@@ -28,12 +27,14 @@ export default App;
2827
```
2928

3029
### Options
30+
3131
`<Retool>` expects a `url` prop pointing to an embedded Retool application. You can generate this URL in the editor mode of a Retool app by clicking "Share" then "Public".
3232

33+
`<Retool>` will accept an optional `data` object, which is made available to the embedded application. When an embedded Retool application runs a Parent Window Query, `<Retool>` will check if `data` contains a key matching the Parent Window Query's selector, and if so, return that value to the query.
3334

3435
### Example
3536

36-
Running `yarn start` will start an application with a basic Retool app embeded.
37+
Running `yarn start` will start an application with a basic Retool app embeded.
3738

3839
There is a live example here: [https://react-retool.surge.sh](https://react-retool.surge.sh)
3940

@@ -52,6 +53,6 @@ You will also see any lint errors in the console.
5253
## Publishing
5354

5455
1. Bump version with `npm version [major|minor|patch]`
55-
2. Run `yarn publish:npm`. This will build the project in the `/dst` directory.
56+
2. Run `yarn publish:npm`. This will build the project in the `/dst` directory.
5657
3. Navigate to `/dst` directory.
5758
4. Publish to npm with `npm publish`

src/App.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1-
import './App.css';
2-
import Retool from './components/Retool'
1+
import Retool from "./components/Retool"
32

43
function App() {
5-
return (
6-
<Retool url="https://retoolin.tryretool.com/embedded/public/f7607e1f-670a-4ebf-9a09-be54cf17181e"></Retool>
7-
);
4+
return (
5+
// <Retool url="https://retoolin.tryretool.com/embedded/public/f7607e1f-670a-4ebf-9a09-be54cf17181e"></Retool>
6+
<Retool
7+
data={{
8+
example: "value",
9+
}}
10+
url="https://example.retool.com/embedded/public/fb30f045-f2d3-461d-9b19-ddfee7357986"
11+
></Retool>
12+
)
813
}
914

10-
export default App;
15+
export default App

src/components/Retool.js

Lines changed: 122 additions & 110 deletions
Original file line numberDiff line numberDiff line change
@@ -1,120 +1,132 @@
1-
import React from "react";
2-
import "./retool.css";
1+
import React from "react"
32

43
class Retool extends React.Component {
5-
constructor(props) {
6-
super(props);
7-
8-
if (!this.props.url)
9-
throw new Error("Please pass a url into the Retool component.");
10-
11-
this.state = {
12-
url: props.url,
13-
elementWatchers: {},
14-
};
15-
}
16-
17-
componentDidMount() {
18-
this.startListening();
19-
this.startWatchers();
20-
}
21-
22-
startListening = () => {
23-
if (this.iframe) {
24-
window.addEventListener("message", (e) => this.handle(e));
4+
constructor(props) {
5+
super(props)
6+
7+
if (!this.props.url)
8+
throw new Error("Please pass a url into the Retool component.")
9+
10+
this.state = {
11+
url: props.url,
12+
elementWatchers: {},
13+
parentData: this.props.data || {},
14+
}
15+
}
16+
17+
componentDidMount() {
18+
this.startListening()
19+
this.startWatchers()
20+
}
21+
22+
startListening = () => {
23+
if (this.iframe) {
24+
window.addEventListener("message", (e) => this.handle(e))
25+
}
26+
}
27+
28+
startWatchers = () => {
29+
var watcherKeys = Object.keys(this.state.elementWatchers)
30+
31+
for (var i = 0; i < watcherKeys.length; i++) {
32+
var key = watcherKeys[i]
33+
var watcher = this.state.elementWatchers[key]
34+
var selector = watcher.selector
35+
const value = this.dataFromSelector(selector)
36+
if (value !== watcher.prevValue) {
37+
watcher.prevValue = value
38+
watcher.iframe.contentWindow.postMessage(
39+
{
40+
type: "PARENT_WINDOW_RESULT",
41+
result: value,
42+
id: watcher.queryId,
43+
pageName: watcher.pageName,
44+
},
45+
"*"
46+
)
47+
}
48+
}
49+
50+
setTimeout(this.startWatchers, 100)
2551
}
26-
};
27-
28-
startWatchers = () => {
29-
var watcherKeys = Object.keys(this.state.elementWatchers);
30-
31-
for (var i = 0; i < watcherKeys.length; i++) {
32-
var key = watcherKeys[i];
33-
var watcher = this.state.elementWatchers[key];
34-
var selector = watcher.selector;
35-
var node = document.querySelector(selector);
36-
var value = node?.textContent;
37-
if (value !== watcher.prevValue) {
38-
watcher.prevValue = value;
39-
watcher.iframe.contentWindow.postMessage(
40-
{
41-
type: "PARENT_WINDOW_RESULT",
42-
result: value,
43-
id: watcher.queryId,
44-
pageName: watcher.pageName,
45-
},
46-
"*"
47-
);
48-
}
52+
53+
createOrReplaceWatcher = (selector, pageName, queryId) => {
54+
var watcherId = pageName + "-" + queryId
55+
var watchers = { ...this.state.elementWatchers }
56+
57+
watchers[watcherId] = {
58+
iframe: this.iframe,
59+
selector: selector,
60+
pageName: pageName,
61+
queryId: queryId,
62+
prevValue: null,
63+
}
64+
65+
this.setState({ elementWatchers: watchers })
66+
}
67+
68+
dataFromSelector = (selector) => {
69+
// Two places the app might be asking for data:
70+
// 1. The textContent of an HTML element.
71+
// 2. From data passed into this component
72+
const matchingInjectedData = this.state.parentData[selector]
73+
const nodeData = document.querySelector(selector)?.textContent
74+
return matchingInjectedData || nodeData || null
75+
}
76+
77+
postMessageForSelector = (messageType, eventData) => {
78+
const maybeData = this.dataFromSelector(eventData.selector)
79+
80+
if (maybeData) {
81+
this.iframe.contentWindow.postMessage(
82+
{
83+
type: messageType,
84+
result: maybeData,
85+
id: eventData.id,
86+
pageName: eventData.pageName,
87+
},
88+
"*"
89+
)
90+
} else {
91+
console.log(
92+
`Not sending data back to Retool, nothing found for selector: ${eventData.selector}`
93+
)
94+
}
4995
}
5096

51-
setTimeout(this.startWatchers, 100);
52-
};
53-
54-
createOrReplaceWatcher = (selector, pageName, queryId) => {
55-
var watcherId = pageName + "-" + queryId;
56-
var watchers = { ...this.state.elementWatchers };
57-
58-
watchers[watcherId] = {
59-
iframe: this.iframe,
60-
selector: selector,
61-
pageName: pageName,
62-
queryId: queryId,
63-
prevValue: null,
64-
};
65-
66-
this.setState({ elementWatchers: watchers });
67-
};
68-
69-
handle = (event) => {
70-
if (!this.iframe.contentWindow) return;
71-
72-
var node;
73-
74-
if (event.data.type === "PARENT_WINDOW_QUERY") {
75-
node = document.querySelector(event.data.selector);
76-
this.createOrReplaceWatcher(
77-
event.data.selector,
78-
event.data.pageName,
79-
event.data.id
80-
);
81-
82-
this.iframe.contentWindow.postMessage(
83-
{
84-
type: "PARENT_WINDOW_RESULT",
85-
result: node?.textContent,
86-
id: event.data.id,
87-
pageName: event.data.pageName,
88-
},
89-
"*"
90-
);
97+
handle = (event) => {
98+
if (!this.iframe.contentWindow) return
99+
if (event.data.type === "PARENT_WINDOW_QUERY") {
100+
this.createOrReplaceWatcher(
101+
event.data.selector,
102+
event.data.pageName,
103+
event.data.id
104+
)
105+
this.postMessageForSelector("PARENT_WINDOW_RESULT", event.data)
106+
}
107+
108+
if (event.data.type === "PARENT_WINDOW_PREVIEW_QUERY") {
109+
this.postMessageForSelector(
110+
"PARENT_WINDOW_PREVIEW_RESULT",
111+
event.data
112+
)
113+
}
91114
}
92115

93-
if (event.data.type === "PARENT_WINDOW_PREVIEW_QUERY") {
94-
node = document.querySelector(event.data.selector);
95-
this.iframe.contentWindow.postMessage(
96-
{
97-
type: "PARENT_WINDOW_PREVIEW_RESULT",
98-
result: node?.textContent,
99-
id: event.data.id,
100-
},
101-
"*"
102-
);
116+
render() {
117+
return (
118+
<iframe
119+
height="100%"
120+
width="100%"
121+
frameBorder="none"
122+
src={this.state.url}
123+
ref={(e) => {
124+
this.iframe = e
125+
}}
126+
title="retool"
127+
></iframe>
128+
)
103129
}
104-
};
105-
106-
render() {
107-
return (
108-
<iframe
109-
frameBorder="none"
110-
src={this.state.url}
111-
ref={(e) => {
112-
this.iframe = e;
113-
}}
114-
title="retool"
115-
></iframe>
116-
);
117-
}
118130
}
119131

120-
export default Retool;
132+
export default Retool

src/components/retool.css

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +0,0 @@
1-
iframe {
2-
width: 100%;
3-
height: 100%
4-
}

0 commit comments

Comments
 (0)