-
Notifications
You must be signed in to change notification settings - Fork 37
Expand file tree
/
Copy pathwidget.js
More file actions
130 lines (123 loc) · 4.57 KB
/
widget.js
File metadata and controls
130 lines (123 loc) · 4.57 KB
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
129
/**
* Recursively transforms tag, a JSON representation of an instance of a
* React component and its children, into a React element suitable for
* passing to ReactDOM.render.
* @param {Object} components
* @param {Object} tag
*/
export function hydrate(components, tag) {
if (typeof tag === 'string') return tag;
if (isDependency(tag)) return null;
if (tag.name[0] === tag.name[0].toUpperCase()
&& !components.hasOwnProperty(tag.name)) {
throw new Error("Unknown component: " + tag.name);
}
// thanks https://github.com/stla
// https://github.com/react-R/reactR/issues/61
// attribs that are slots will likely contain tags that also need hydration
// convert any attribs that contain object that matches tag structure
for(let attrib in tag.attribs) {
if(isTag(tag.attribs[attrib])) {
tag.attribs[attrib] = hydrate(components, tag.attribs[attrib]);
}
}
var elem = components.hasOwnProperty(tag.name) ? components[tag.name] : tag.name,
args = [elem, tag.attribs];
for (var i = 0; i < tag.children.length; i++) {
args.push(hydrate(components, tag.children[i]));
}
return React.createElement.apply(React, args);
}
export const defaultOptions = {
// The name of the property on the root tag to use for the width, if
// it's updated.
widthProperty: "width",
// The name of the property on the root tag to use for the height, if
// it's updated.
heightProperty: "height",
// Whether or not to append the string 'px' to the width and height
// properties when they change.
appendPx: false,
// Whether or not to dynamically update the width and height properties
// of the last known tag when the computed width and height change in
// the browser.
renderOnResize: false
};
export function mergeOptions(options) {
var merged = {};
for (var k in defaultOptions) {
merged[k] = defaultOptions[k];
}
for (var k in options) {
if (!defaultOptions.hasOwnProperty(k)) {
throw new Error("Unrecognized option: " + k);
}
merged[k] = options[k];
}
return merged;
}
export function formatDimension(dim, options) {
if (options.appendPx) {
return dim + 'px';
} else {
return dim;
}
}
export function isTag(value) {
return (typeof value === 'object')
&& value.hasOwnProperty('name')
&& value.hasOwnProperty('attribs')
&& value.hasOwnProperty('children');
}
export function isDependency(value) {
return (typeof value === 'object')
&& value.hasOwnProperty('name')
&& value.hasOwnProperty('version')
&& value.hasOwnProperty('src')
&& value.hasOwnProperty('script');
}
/**
* Creates an HTMLWidget that is updated by rendering a React component.
* React component constructors are made available by specifying them by
* name in the components object.
* @param {string} name
* @param {string} type
* @param {Object} components
* @param {Object} options
*/
export function reactWidget(name, type, components, options) {
var actualOptions = mergeOptions(options);
window.HTMLWidgets.widget({
name: name,
type: type,
factory: function (el, width, height) {
var lastValue,
instance = {},
renderValue = (function (value) {
if (actualOptions.renderOnResize) {
// value.tag might be a primitive string, in which
// case there is no attribs property.
if (typeof value.tag === 'object') {
value.tag.attribs[actualOptions["widthProperty"]] = formatDimension(width);
value.tag.attribs[actualOptions["heightProperty"]] = formatDimension(height);
}
lastValue = value;
}
// with functional stateless components this will be null
// see https://reactjs.org/docs/react-dom.html#render for more details
this.instance.component = ReactDOM.render(hydrate(components, value.tag), el);
});
return {
instance: instance,
renderValue: renderValue,
resize: function (newWidth, newHeight) {
if (actualOptions.renderOnResize) {
width = newWidth;
height = newHeight;
renderValue(lastValue);
}
}
};
}
})
}