-
Notifications
You must be signed in to change notification settings - Fork 30
/
Copy pathindex.jsx
128 lines (103 loc) · 3.5 KB
/
index.jsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import React from 'react';
import { PropTypes as RPT } from 'prop-types';
export default class Script extends React.Component {
static propTypes = {
attributes: RPT.object, // eslint-disable-line react/forbid-prop-types
onCreate: RPT.func,
onError: RPT.func.isRequired,
onLoad: RPT.func.isRequired,
url: RPT.string.isRequired,
};
static defaultProps = {
attributes: {},
onCreate: () => {},
onError: () => {},
onLoad: () => {},
}
// A dictionary mapping script URLs to a dictionary mapping
// component key to component for all components that are waiting
// for the script to load.
static scriptObservers = {};
// A dictionary mapping script URL to a boolean value indicating if the script
// has already been loaded.
static loadedScripts = {};
// A dictionary mapping script URL to a boolean value indicating if the script
// has failed to load.
static erroredScripts = {};
// A counter used to generate a unique id for each component that uses
// ScriptLoaderMixin.
static idCount = 0;
constructor(props) {
super(props);
this.scriptLoaderId = `id${this.constructor.idCount++}`; // eslint-disable-line space-unary-ops, no-plusplus
}
componentDidMount() {
const { onError, onLoad, url } = this.props;
if (this.constructor.loadedScripts[url]) {
onLoad();
return;
}
if (this.constructor.erroredScripts[url]) {
onError();
return;
}
// If the script is loading, add the component to the script's observers
// and return. Otherwise, initialize the script's observers with the component
// and start loading the script.
if (this.constructor.scriptObservers[url]) {
this.constructor.scriptObservers[url][this.scriptLoaderId] = this.props;
return;
}
this.constructor.scriptObservers[url] = { [this.scriptLoaderId]: this.props };
this.createScript();
}
componentWillUnmount() {
const { url } = this.props;
const observers = this.constructor.scriptObservers[url];
// If the component is waiting for the script to load, remove the
// component from the script's observers before unmounting the component.
if (observers) {
delete observers[this.scriptLoaderId];
}
}
createScript() {
const { onCreate, url, attributes } = this.props;
const script = document.createElement('script');
onCreate();
// add 'data-' or non standard attributes to the script tag
if (attributes) {
Object.keys(attributes).forEach(prop => script.setAttribute(prop, attributes[prop]));
}
script.src = url;
// default async to true if not set with custom attributes
if (!script.hasAttribute('async')) {
script.async = 1;
}
const callObserverFuncAndRemoveObserver = (shouldRemoveObserver) => {
const observers = this.constructor.scriptObservers[url];
Object.keys(observers).forEach((key) => {
if (shouldRemoveObserver(observers[key])) {
delete this.constructor.scriptObservers[url][this.scriptLoaderId];
}
});
};
script.onload = () => {
this.constructor.loadedScripts[url] = true;
callObserverFuncAndRemoveObserver((observer) => {
observer.onLoad();
return true;
});
};
script.onerror = () => {
this.constructor.erroredScripts[url] = true;
callObserverFuncAndRemoveObserver((observer) => {
observer.onError();
return true;
});
};
document.body.appendChild(script);
}
render() {
return null;
}
}