@ea-lab/reactive-json 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -0
- package/dist/reactive-json.css +5 -0
- package/dist/reactive-json.js +56303 -0
- package/dist/reactive-json.umd.cjs +382 -0
- package/lib/component/action/HashChangeListener.jsx +66 -0
- package/lib/component/action/Hide.jsx +14 -0
- package/lib/component/action/MessageListener.jsx +62 -0
- package/lib/component/action/Popover.jsx +53 -0
- package/lib/component/action/ReactOnEvent.jsx +118 -0
- package/lib/component/action/Redirect.jsx +26 -0
- package/lib/component/action/Tooltip.jsx +27 -0
- package/lib/component/action/VisuallyHide.jsx +15 -0
- package/lib/component/element/chart/BarChart.jsx +40 -0
- package/lib/component/element/chart/DoughnutChart.jsx +32 -0
- package/lib/component/element/chart/LineChart.jsx +40 -0
- package/lib/component/element/chart/PolarAreaChart.jsx +32 -0
- package/lib/component/element/form/CheckBoxField.jsx +215 -0
- package/lib/component/element/form/DateField.jsx +42 -0
- package/lib/component/element/form/NumberField.jsx +29 -0
- package/lib/component/element/form/SelectField.jsx +130 -0
- package/lib/component/element/form/TextAreaField.jsx +48 -0
- package/lib/component/element/form/TextField.jsx +65 -0
- package/lib/component/element/form/formElementsCommon.jsx +54 -0
- package/lib/component/element/html/AccordionItem.jsx +42 -0
- package/lib/component/element/html/FolderSortableTree.jsx +307 -0
- package/lib/component/element/html/FormatNumeral.jsx +118 -0
- package/lib/component/element/html/Html.jsx +107 -0
- package/lib/component/element/html/LabelFromValue.jsx +89 -0
- package/lib/component/element/html/Modal.jsx +77 -0
- package/lib/component/element/html/ModalForm.jsx +30 -0
- package/lib/component/element/html/Paragraph.jsx +10 -0
- package/lib/component/element/html/PreformattedMarkup.jsx +54 -0
- package/lib/component/element/html/SortableTreeItemCollapseButton.jsx +20 -0
- package/lib/component/element/html/Tabs.jsx +55 -0
- package/lib/component/element/special/BootstrapElement.jsx +32 -0
- package/lib/component/element/special/Count.jsx +46 -0
- package/lib/component/element/special/DataFilter.jsx +156 -0
- package/lib/component/element/special/DelayedActions.jsx +119 -0
- package/lib/component/element/special/PageControls.jsx +19 -0
- package/lib/component/element/special/Phantom.jsx +25 -0
- package/lib/component/element/special/Switch.jsx +131 -0
- package/lib/component/hook/usePagination.jsx +184 -0
- package/lib/component/reaction/addData.jsx +23 -0
- package/lib/component/reaction/fetchData.jsx +83 -0
- package/lib/component/reaction/moveData.jsx +52 -0
- package/lib/component/reaction/postMessage.jsx +43 -0
- package/lib/component/reaction/redirectNow.jsx +17 -0
- package/lib/component/reaction/removeData.jsx +48 -0
- package/lib/component/reaction/setClipboardData.jsx +20 -0
- package/lib/component/reaction/setData.jsx +23 -0
- package/lib/component/reaction/submitData.jsx +136 -0
- package/lib/component/reaction/triggerEvent.jsx +62 -0
- package/lib/component/utility/formatString.jsx +59 -0
- package/lib/engine/Actions.jsx +392 -0
- package/lib/engine/EventDispatcherContext.jsx +16 -0
- package/lib/engine/EventDispatcherProvider.jsx +80 -0
- package/lib/engine/GlobalDataContext.jsx +13 -0
- package/lib/engine/GlobalDataContextProvider.jsx +33 -0
- package/lib/engine/PaginationContext.jsx +10 -0
- package/lib/engine/PaginationProvider.jsx +61 -0
- package/lib/engine/ReactiveJsonRoot.jsx +315 -0
- package/lib/engine/TemplateContext.jsx +13 -0
- package/lib/engine/TemplateSystem.jsx +302 -0
- package/lib/engine/View.jsx +240 -0
- package/lib/main.jsx +41 -0
- package/package.json +72 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import {useContext} from "react";
|
|
2
|
+
import GlobalDataContext from "./GlobalDataContext";
|
|
3
|
+
import TemplateContext from "./TemplateContext";
|
|
4
|
+
import {normalizeAttributesForReactJsx} from "../component/element/html/Html";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Transforms a data location string to a path to be used in the UI components.
|
|
8
|
+
*
|
|
9
|
+
* @param dataLocation
|
|
10
|
+
* @param currentPath
|
|
11
|
+
* @param globalDataContext
|
|
12
|
+
* @param templateContext
|
|
13
|
+
* @returns {string|*}
|
|
14
|
+
* @constructor
|
|
15
|
+
*/
|
|
16
|
+
export const dataLocationToPath = ({dataLocation, currentPath, globalDataContext, templateContext}) => {
|
|
17
|
+
if (!(typeof dataLocation === "string") || !(dataLocation.startsWith("~.") || dataLocation.startsWith("~~.") || dataLocation.startsWith("~>"))) {
|
|
18
|
+
if ("~" === dataLocation) {
|
|
19
|
+
// The data location is the template root.
|
|
20
|
+
return templateContext.templatePath;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if ("~~" === dataLocation) {
|
|
24
|
+
// The data location is the global template root.
|
|
25
|
+
return globalDataContext.templatePath;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// This value is not a data location.
|
|
29
|
+
// Render what is given as is.
|
|
30
|
+
return dataLocation;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// This tells if we check in the current template context, the global data, or the current path.
|
|
34
|
+
let pathBase;
|
|
35
|
+
|
|
36
|
+
if (dataLocation.startsWith("~~.")) {
|
|
37
|
+
// Build the path starting from the global data context path (in theory, just "data").
|
|
38
|
+
pathBase = globalDataContext.templatePath;
|
|
39
|
+
} else if (dataLocation.startsWith("~.")) {
|
|
40
|
+
// Build the path starting from the current template path.
|
|
41
|
+
pathBase = templateContext.templatePath;
|
|
42
|
+
} else if (dataLocation.startsWith("~>")) {
|
|
43
|
+
// Build the path starting from an ascendant of the current template path.
|
|
44
|
+
const keyToFind = dataLocation.substring(2, dataLocation.indexOf("."));
|
|
45
|
+
|
|
46
|
+
if (!templateContext.templatePath.includes(keyToFind)) {
|
|
47
|
+
throw new Error(keyToFind + " not found in the current template path.");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const keyToFindIndex = templateContext.templatePath.indexOf(keyToFind);
|
|
51
|
+
|
|
52
|
+
pathBase = templateContext.templatePath.substring(0, keyToFindIndex + keyToFind.length);
|
|
53
|
+
} else {
|
|
54
|
+
pathBase = currentPath;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const splitLocationArray = dataLocation.split(".");
|
|
58
|
+
|
|
59
|
+
// Remove the template value detection character.
|
|
60
|
+
splitLocationArray.shift();
|
|
61
|
+
|
|
62
|
+
return pathBase + "." + splitLocationArray.join(".");
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Evaluates the given attributes with the given contexts.
|
|
67
|
+
*
|
|
68
|
+
* @param {{}} attrs
|
|
69
|
+
* @param {{}} globalDataContext
|
|
70
|
+
* @param {{}} templateContext
|
|
71
|
+
* @param {{normalizeBeforeEvaluation : boolean}} options normalizeBeforeEvaluation is false if unset.
|
|
72
|
+
*
|
|
73
|
+
* @returns {{}}
|
|
74
|
+
*/
|
|
75
|
+
export const evaluateAttributes = ({attrs, globalDataContext, templateContext, options = {}}) => {
|
|
76
|
+
const evaluated = {};
|
|
77
|
+
|
|
78
|
+
if (!attrs) {
|
|
79
|
+
return evaluated;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const normalized = options.normalizeBeforeEvaluation ? normalizeAttributesForReactJsx(attrs) : attrs;
|
|
83
|
+
|
|
84
|
+
for (const attrName of Object.keys(normalized)) {
|
|
85
|
+
// This will replace the value by the template value if it's a valid reference.
|
|
86
|
+
// We call directly the TemplateValue component as a function to evaluate the attribute value.
|
|
87
|
+
const evaluatedAttr = evaluateTemplateValue({
|
|
88
|
+
globalDataContext: globalDataContext,
|
|
89
|
+
templateContext: templateContext,
|
|
90
|
+
valueToEvaluate: normalized[attrName]
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// We only keep the attribute if it can be represented as an attribute value
|
|
94
|
+
// or be interpreted by React such as callbacks.
|
|
95
|
+
if (evaluatedAttr) {
|
|
96
|
+
evaluated[attrName] = evaluatedAttr;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// TODO: the next code block has been commented out, because we found that
|
|
100
|
+
// it may be more useful to allow objects as attributes (e.g. the style attribute
|
|
101
|
+
// which is an object). Remove this block after testing.
|
|
102
|
+
// switch (typeof evaluatedAttr) {
|
|
103
|
+
// case "string":
|
|
104
|
+
// case "bigint":
|
|
105
|
+
// case "number":
|
|
106
|
+
// case "boolean":
|
|
107
|
+
// case "function":
|
|
108
|
+
// evaluated[attrName] = evaluatedAttr;
|
|
109
|
+
// break;
|
|
110
|
+
//
|
|
111
|
+
// default:
|
|
112
|
+
// // We only keep the attribute if it can be represented as an attribute value
|
|
113
|
+
// // or be interpreted by React such as callbacks.
|
|
114
|
+
// delete evaluated[attrName];
|
|
115
|
+
// break;
|
|
116
|
+
// }
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return evaluated;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Evaluates the template value using the given template and global context.
|
|
124
|
+
*
|
|
125
|
+
* @param valueToEvaluate The value to evaluate.
|
|
126
|
+
* @param globalDataContext The global data context.
|
|
127
|
+
* @param templateContext The template context.
|
|
128
|
+
* @returns {undefined|*}
|
|
129
|
+
*/
|
|
130
|
+
export const evaluateTemplateValue = ({valueToEvaluate, globalDataContext, templateContext}) => {
|
|
131
|
+
if (!isTemplateValue(valueToEvaluate)) {
|
|
132
|
+
// This value does not use the template context data.
|
|
133
|
+
// Render what is given as is.
|
|
134
|
+
return valueToEvaluate;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/*
|
|
138
|
+
* Experimental zone.
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
if ("~" === valueToEvaluate) {
|
|
142
|
+
// We want the whole template data.
|
|
143
|
+
return templateContext.templateData;
|
|
144
|
+
} else if ("~~" === valueToEvaluate) {
|
|
145
|
+
// We want the whole global template data.
|
|
146
|
+
return globalDataContext.templateData;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/*
|
|
150
|
+
* End of experimental zone.
|
|
151
|
+
*/
|
|
152
|
+
|
|
153
|
+
let currentNode;
|
|
154
|
+
|
|
155
|
+
if (valueToEvaluate.startsWith("~~.")) {
|
|
156
|
+
// Start from the global data context node.
|
|
157
|
+
currentNode = globalDataContext?.templateData;
|
|
158
|
+
} else if (valueToEvaluate.startsWith("~>")) {
|
|
159
|
+
// Start from the global data context node, but evaluate the "valueToEvaluate"
|
|
160
|
+
// to use one of the ascending nodes of the current template.
|
|
161
|
+
valueToEvaluate = dataLocationToPath({
|
|
162
|
+
dataLocation: valueToEvaluate,
|
|
163
|
+
currentPath: templateContext.templatePath,
|
|
164
|
+
globalDataContext,
|
|
165
|
+
templateContext,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
currentNode = globalDataContext?.templateData;
|
|
169
|
+
} else {
|
|
170
|
+
// Start from the current template context node.
|
|
171
|
+
currentNode = templateContext?.templateData;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (!currentNode) {
|
|
175
|
+
// No context supplied. This is likely a bug; contexts must be supplied when calling this function.
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Find the value in the template context.
|
|
180
|
+
const splitValueArray = valueToEvaluate.split(".");
|
|
181
|
+
|
|
182
|
+
// Remove the template value detection character.
|
|
183
|
+
splitValueArray.shift();
|
|
184
|
+
|
|
185
|
+
while (splitValueArray.length) {
|
|
186
|
+
if (typeof currentNode !== "object") {
|
|
187
|
+
// Not an object, so there is no need to continue.
|
|
188
|
+
// Return an undefined value.
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
currentNode = currentNode[splitValueArray.shift()];
|
|
193
|
+
|
|
194
|
+
if (currentNode === undefined) {
|
|
195
|
+
// No need to continue.
|
|
196
|
+
// Return an undefined value.
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return currentNode;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Evaluates an array or object containing values to evaluate.
|
|
206
|
+
*
|
|
207
|
+
* You can also pass a single value to evaluate.
|
|
208
|
+
*
|
|
209
|
+
* @param {Array|object|*} valueToEvaluate The value to evaluate. Usually a string, an array, or an object.
|
|
210
|
+
* @param {{}} globalDataContext The global data context values.
|
|
211
|
+
* @param {{}} templateContext The current template context values.
|
|
212
|
+
*
|
|
213
|
+
* @returns {*} The evaluated value. It tries to keep the same structure (array, object, single) as the given value.
|
|
214
|
+
*/
|
|
215
|
+
export const evaluateTemplateValueCollection = ({valueToEvaluate, globalDataContext, templateContext}) => {
|
|
216
|
+
let evaluated;
|
|
217
|
+
|
|
218
|
+
if (typeof valueToEvaluate === "object") {
|
|
219
|
+
// Evaluate any first level values.
|
|
220
|
+
// Deep (recursive) evaluation is technically possible,
|
|
221
|
+
// but we are not doing this yet for performance and usefulness reasons.
|
|
222
|
+
evaluated = Array.isArray(valueToEvaluate) ? [] : {};
|
|
223
|
+
|
|
224
|
+
for (const [key, itemContent] of Object.entries(valueToEvaluate)) {
|
|
225
|
+
evaluated[key] = evaluateTemplateValue({
|
|
226
|
+
globalDataContext,
|
|
227
|
+
templateContext,
|
|
228
|
+
valueToEvaluate: itemContent
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
// Single value.
|
|
233
|
+
evaluated = evaluateTemplateValue({
|
|
234
|
+
globalDataContext,
|
|
235
|
+
templateContext,
|
|
236
|
+
valueToEvaluate
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return evaluated;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Checks if the given value is a value which can be replaced by the template system.
|
|
245
|
+
* @param valueToEvaluate
|
|
246
|
+
* @returns {string|boolean}
|
|
247
|
+
*/
|
|
248
|
+
export const isTemplateValue = (valueToEvaluate) => {
|
|
249
|
+
if (!(typeof valueToEvaluate === "string") || !(valueToEvaluate.startsWith("~.") || valueToEvaluate.startsWith("~~.") || valueToEvaluate.startsWith("~>") || "~" === valueToEvaluate || "~~" === valueToEvaluate)) {
|
|
250
|
+
// This value does not use the template context data.
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Render what is given as is for chaining.
|
|
255
|
+
return valueToEvaluate;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* A template value is a value that is retrieved from the current template data.
|
|
260
|
+
*
|
|
261
|
+
* @param valueToEvaluate
|
|
262
|
+
*
|
|
263
|
+
* @returns {{}}
|
|
264
|
+
*
|
|
265
|
+
* @constructor
|
|
266
|
+
*/
|
|
267
|
+
const TemplateValue = ({valueToEvaluate}) => {
|
|
268
|
+
const globalDataContext = useContext(GlobalDataContext);
|
|
269
|
+
const templateContext = useContext(TemplateContext);
|
|
270
|
+
|
|
271
|
+
return evaluateTemplateValue({
|
|
272
|
+
globalDataContext: globalDataContext,
|
|
273
|
+
templateContext: templateContext,
|
|
274
|
+
valueToEvaluate: valueToEvaluate,
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
export default TemplateValue;
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Evaluates the given attributes with the given contexts.
|
|
282
|
+
*
|
|
283
|
+
* @param {{}} attrs
|
|
284
|
+
* @param {{normalizeBeforeEvaluation : boolean}} options normalizeBeforeEvaluation = true if unset.
|
|
285
|
+
*
|
|
286
|
+
* @returns {{}} Evaluated attributes.
|
|
287
|
+
*/
|
|
288
|
+
export const useEvaluatedAttributes = (attrs, options = {}) => {
|
|
289
|
+
const globalDataContext = useContext(GlobalDataContext);
|
|
290
|
+
const templateContext = useContext(TemplateContext);
|
|
291
|
+
|
|
292
|
+
return evaluateAttributes(
|
|
293
|
+
{
|
|
294
|
+
attrs,
|
|
295
|
+
globalDataContext,
|
|
296
|
+
templateContext,
|
|
297
|
+
options: options.normalizeBeforeEvaluation === undefined
|
|
298
|
+
? {...options, normalizeBeforeEvaluation: true}
|
|
299
|
+
: options,
|
|
300
|
+
}
|
|
301
|
+
);
|
|
302
|
+
};
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import {useContext} from 'react';
|
|
2
|
+
import BarChart from "../component/element/chart/BarChart";
|
|
3
|
+
import DoughnutChart from "../component/element/chart/DoughnutChart";
|
|
4
|
+
import PolarAreaChart from "../component/element/chart/PolarAreaChart";
|
|
5
|
+
import CheckBoxField from "../component/element/form/CheckBoxField";
|
|
6
|
+
import DateField from "../component/element/form/DateField";
|
|
7
|
+
import NumberField from "../component/element/form/NumberField";
|
|
8
|
+
import SelectField from "../component/element/form/SelectField";
|
|
9
|
+
import TextAreaField from "../component/element/form/TextAreaField";
|
|
10
|
+
import TextField from "../component/element/form/TextField";
|
|
11
|
+
import AccordionItem from "../component/element/html/AccordionItem";
|
|
12
|
+
import FolderSortableTree from "../component/element/html/FolderSortableTree";
|
|
13
|
+
import FormatNumeral from "../component/element/html/FormatNumeral";
|
|
14
|
+
import Html from "../component/element/html/Html";
|
|
15
|
+
import LabelFromValue from "../component/element/html/LabelFromValue";
|
|
16
|
+
import Modal from "../component/element/html/Modal";
|
|
17
|
+
import Paragraph from "../component/element/html/Paragraph";
|
|
18
|
+
import PreformattedMarkup from "../component/element/html/PreformattedMarkup";
|
|
19
|
+
import SortableTreeItemCollapseButton from "../component/element/html/SortableTreeItemCollapseButton";
|
|
20
|
+
import Tabs from "../component/element/html/Tabs";
|
|
21
|
+
import BootstrapElement from "../component/element/special/BootstrapElement";
|
|
22
|
+
import Count from "../component/element/special/Count";
|
|
23
|
+
import DataFilter from "../component/element/special/DataFilter";
|
|
24
|
+
import DelayedActions from "../component/element/special/DelayedActions";
|
|
25
|
+
import PageControls from "../component/element/special/PageControls";
|
|
26
|
+
import Phantom from "../component/element/special/Phantom";
|
|
27
|
+
import Switch from "../component/element/special/Switch";
|
|
28
|
+
import GlobalDataContext from "./GlobalDataContext";
|
|
29
|
+
import TemplateContext from "./TemplateContext";
|
|
30
|
+
import TemplateValue, {dataLocationToPath, evaluateTemplateValue} from "./TemplateSystem";
|
|
31
|
+
import {
|
|
32
|
+
Accordion,
|
|
33
|
+
Alert,
|
|
34
|
+
Badge,
|
|
35
|
+
Button,
|
|
36
|
+
} from "react-bootstrap";
|
|
37
|
+
import LineChart from "../component/element/chart/LineChart";
|
|
38
|
+
|
|
39
|
+
const components = {
|
|
40
|
+
AccordionItem,
|
|
41
|
+
BarChart,
|
|
42
|
+
CheckBoxField,
|
|
43
|
+
Count,
|
|
44
|
+
DateField,
|
|
45
|
+
DataFilter,
|
|
46
|
+
DelayedActions,
|
|
47
|
+
DoughnutChart,
|
|
48
|
+
FolderSortableTree,
|
|
49
|
+
FormatNumeral,
|
|
50
|
+
Html,
|
|
51
|
+
LabelFromValue,
|
|
52
|
+
LineChart,
|
|
53
|
+
Modal,
|
|
54
|
+
NumberField,
|
|
55
|
+
PageControls,
|
|
56
|
+
Paragraph,
|
|
57
|
+
Phantom,
|
|
58
|
+
PolarAreaChart,
|
|
59
|
+
PreformattedMarkup,
|
|
60
|
+
SelectField,
|
|
61
|
+
SortableTreeItemCollapseButton,
|
|
62
|
+
Switch,
|
|
63
|
+
Tabs,
|
|
64
|
+
TextAreaField,
|
|
65
|
+
TextField,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Gives direct access to React Bootstrap components.
|
|
70
|
+
*/
|
|
71
|
+
const bootstrapComponents = {
|
|
72
|
+
BsAccordion: Accordion,
|
|
73
|
+
BsAlert: Alert,
|
|
74
|
+
BsBadge: Badge,
|
|
75
|
+
BsButton: Button,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
function View({props, currentData, datafield, path}) {
|
|
79
|
+
const globalDataContext = useContext(GlobalDataContext);
|
|
80
|
+
const templateContext = useContext(TemplateContext);
|
|
81
|
+
|
|
82
|
+
const {element} = globalDataContext;
|
|
83
|
+
|
|
84
|
+
if (currentData === undefined) {
|
|
85
|
+
currentData = "";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (props?.type) {
|
|
89
|
+
// A type is specified.
|
|
90
|
+
// First, try to find a component matching the given type by name.
|
|
91
|
+
// When not found, we map to a Html component as fallback.
|
|
92
|
+
let componentRegistryId = undefined;
|
|
93
|
+
let ComponentToRender = undefined;
|
|
94
|
+
|
|
95
|
+
const componentRegistries = [
|
|
96
|
+
{"registryId": "module", "components": components},
|
|
97
|
+
{"registryId": "bootstrap", "components": bootstrapComponents},
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
while (componentRegistries.length) {
|
|
101
|
+
const {registryId, components: registryComponents} = componentRegistries.shift();
|
|
102
|
+
|
|
103
|
+
ComponentToRender = registryComponents[props.type] ?? undefined;
|
|
104
|
+
|
|
105
|
+
if (ComponentToRender !== undefined) {
|
|
106
|
+
componentRegistryId = registryId;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (ComponentToRender === undefined) {
|
|
112
|
+
// Use the module:Html component as fallback.
|
|
113
|
+
ComponentToRender = Html;
|
|
114
|
+
componentRegistryId = "module";
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (componentRegistryId === "bootstrap") {
|
|
118
|
+
return <BootstrapElement
|
|
119
|
+
bsComponent={ComponentToRender}
|
|
120
|
+
path={path}
|
|
121
|
+
props={props}
|
|
122
|
+
currentData={currentData}
|
|
123
|
+
datafield={datafield}/>;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (Html === ComponentToRender) {
|
|
127
|
+
// Either the user has specifically asked for a Html component,
|
|
128
|
+
// or this is a fallback for an unknown type.
|
|
129
|
+
// Make sure the tag is set.
|
|
130
|
+
props.tag = props.tag ?? props.type;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return <ComponentToRender
|
|
134
|
+
path={path}
|
|
135
|
+
props={props}
|
|
136
|
+
currentData={currentData}
|
|
137
|
+
datafield={datafield}/>;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (props?.load) {
|
|
141
|
+
// An external render source must be loaded.
|
|
142
|
+
// let load = props.load;
|
|
143
|
+
let loadedRenderArray;
|
|
144
|
+
|
|
145
|
+
const _customDataLocation = props?.customDataLocation ?? undefined;
|
|
146
|
+
if (_customDataLocation) debugger;
|
|
147
|
+
|
|
148
|
+
// Determine which data to use.
|
|
149
|
+
const finalCurrentData = _customDataLocation
|
|
150
|
+
// The data is located somewhere in the current data.
|
|
151
|
+
? evaluateTemplateValue({
|
|
152
|
+
globalDataContext: globalDataContext,
|
|
153
|
+
templateContext: templateContext,
|
|
154
|
+
valueToEvaluate: _customDataLocation,
|
|
155
|
+
})
|
|
156
|
+
// The data is the current data.
|
|
157
|
+
: currentData;
|
|
158
|
+
|
|
159
|
+
// The data path must be set accordingly.
|
|
160
|
+
const finalDataPath = _customDataLocation
|
|
161
|
+
? dataLocationToPath({dataLocation: _customDataLocation, currentPath: path, globalDataContext, templateContext})
|
|
162
|
+
: path;
|
|
163
|
+
|
|
164
|
+
// This external source can return a single component to render,
|
|
165
|
+
// or a collection of components.
|
|
166
|
+
if (typeof props.load === "function") {
|
|
167
|
+
// A JS function has been defined. Execute it with the currentData.
|
|
168
|
+
// The function must return a render array.
|
|
169
|
+
loadedRenderArray = props.load(finalCurrentData);
|
|
170
|
+
} else {
|
|
171
|
+
// Load the render array from the registry.
|
|
172
|
+
loadedRenderArray = element[props.load];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Override any values of the registry render array with the current render array,
|
|
176
|
+
// without the properties specific to the "load" method.
|
|
177
|
+
const {load, customDataLocation, ...propsWithoutLoadKey} = props;
|
|
178
|
+
loadedRenderArray = {...loadedRenderArray, ...propsWithoutLoadKey};
|
|
179
|
+
|
|
180
|
+
// Now that we have our render array, recurse on the View component.
|
|
181
|
+
if (props.keepTemplateContext) {
|
|
182
|
+
// Keep the current template context.
|
|
183
|
+
return (
|
|
184
|
+
<View
|
|
185
|
+
currentData={finalCurrentData}
|
|
186
|
+
datafield={datafield}
|
|
187
|
+
path={finalDataPath}
|
|
188
|
+
props={loadedRenderArray}
|
|
189
|
+
/>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// We open a new template context in the process.
|
|
194
|
+
return (
|
|
195
|
+
<TemplateContext.Provider value={{templateData: finalCurrentData, templatePath: finalDataPath}}>
|
|
196
|
+
<View
|
|
197
|
+
currentData={finalCurrentData}
|
|
198
|
+
datafield={datafield}
|
|
199
|
+
path={finalDataPath}
|
|
200
|
+
props={loadedRenderArray}
|
|
201
|
+
/>
|
|
202
|
+
</TemplateContext.Provider>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Try to go deeper to render something.
|
|
207
|
+
if (Array.isArray(props)) {
|
|
208
|
+
return props.map((item, index) =>
|
|
209
|
+
<View
|
|
210
|
+
currentData={currentData[index] ?? undefined}
|
|
211
|
+
datafield={index}
|
|
212
|
+
key={path + "." + index}
|
|
213
|
+
path={path + "." + index}
|
|
214
|
+
props={item ?? undefined}
|
|
215
|
+
/>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (typeof props === "object") {
|
|
220
|
+
return Object.entries(props).map(([itemKey, item]) => {
|
|
221
|
+
return <View
|
|
222
|
+
currentData={currentData[itemKey] ?? undefined}
|
|
223
|
+
datafield={itemKey ?? undefined}
|
|
224
|
+
key={path + "." + itemKey}
|
|
225
|
+
path={path + "." + itemKey}
|
|
226
|
+
props={item}
|
|
227
|
+
/>
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Display the content directly.
|
|
233
|
+
// The content tries to use the currentData in case the data wants to rewrite the output.
|
|
234
|
+
// If not available, we simply use the given props, which is usually a string, which can
|
|
235
|
+
// also be a reference to a template context data.
|
|
236
|
+
// If no props is available, do not render anything.
|
|
237
|
+
return <TemplateValue valueToEvaluate={currentData || (props ?? null)}/>;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export default View;
|
package/lib/main.jsx
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entry point for the lib.
|
|
3
|
+
*/
|
|
4
|
+
import "bootstrap/dist/css/bootstrap.min.css";
|
|
5
|
+
|
|
6
|
+
import ReactiveJsonRoot from "./engine/ReactiveJsonRoot.jsx";
|
|
7
|
+
import {StrictMode} from "react"
|
|
8
|
+
import {createRoot} from "react-dom/client";
|
|
9
|
+
|
|
10
|
+
export {ReactiveJsonRoot};
|
|
11
|
+
|
|
12
|
+
document.querySelectorAll("reactive-json").forEach((element) => {
|
|
13
|
+
// Use this to change the fetch method.
|
|
14
|
+
const maybeMethod = element.dataset?.method;
|
|
15
|
+
|
|
16
|
+
// Get data included in the root element.
|
|
17
|
+
const headersForData_asElements = element.querySelectorAll("data-source-request-header");
|
|
18
|
+
const headersForData = headersForData_asElements.length ? {} : undefined;
|
|
19
|
+
|
|
20
|
+
headersForData_asElements.forEach((headerElement, key, parent) => {
|
|
21
|
+
const headerField = headerElement?.dataset?.headerField;
|
|
22
|
+
const headerValue = headerElement?.dataset?.headerValue;
|
|
23
|
+
|
|
24
|
+
if (!headerField || !headerValue) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
headersForData[headerField] = headerValue;
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// TODO: Retrieve the reactive-json plugins and inject them in ReactiveJsonRoot.
|
|
32
|
+
createRoot(element).render(
|
|
33
|
+
<StrictMode>
|
|
34
|
+
<ReactiveJsonRoot
|
|
35
|
+
dataFetchMethod={maybeMethod}
|
|
36
|
+
dataUrl={element.dataset.url}
|
|
37
|
+
headersForData={headersForData}
|
|
38
|
+
/>
|
|
39
|
+
</StrictMode>,
|
|
40
|
+
);
|
|
41
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ea-lab/reactive-json",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"lib"
|
|
8
|
+
],
|
|
9
|
+
"main": "./dist/reactive-json.umd.cjs",
|
|
10
|
+
"module": "./dist/reactive-json.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"require": "./dist/reactive-json.umd.cjs",
|
|
14
|
+
"import": "./dist/reactive-json.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"dev": "vite",
|
|
19
|
+
"build": "vite build",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"preview": "vite preview"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://bitbucket.org/ea-lab/reactive-json.git"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"build",
|
|
29
|
+
"config",
|
|
30
|
+
"data",
|
|
31
|
+
"dynamic",
|
|
32
|
+
"interactive",
|
|
33
|
+
"json",
|
|
34
|
+
"react",
|
|
35
|
+
"structure",
|
|
36
|
+
"yaml"
|
|
37
|
+
],
|
|
38
|
+
"author": "Quang-Minh DANG <quang-minh@ea-lab.io> (https://ea-lab.io/)",
|
|
39
|
+
"license": "ISC",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://bitbucket.org/ea-lab/reactive-json/issues"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://bitbucket.org/ea-lab/reactive-json#readme",
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@eslint/js": "^9.21.0",
|
|
46
|
+
"@types/node": "^22.14.1",
|
|
47
|
+
"@types/react": "^18",
|
|
48
|
+
"@types/react-dom": "^18",
|
|
49
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
50
|
+
"bootstrap": "^5.3.5",
|
|
51
|
+
"eslint": "^9.21.0",
|
|
52
|
+
"eslint-plugin-react-hooks": "^5.1.0",
|
|
53
|
+
"eslint-plugin-react-refresh": "^0.4.19",
|
|
54
|
+
"globals": "^15.15.0",
|
|
55
|
+
"react-router-dom": "^7.5.0",
|
|
56
|
+
"vite": "^6.2.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"axios": "^1.8.4",
|
|
60
|
+
"chart.js": "^4.4.8",
|
|
61
|
+
"clsx": "^2.1.1",
|
|
62
|
+
"dnd-kit-sortable-tree": "^0.1.73",
|
|
63
|
+
"html-react-parser": "^5.2.3",
|
|
64
|
+
"js-yaml": "^4.1.0",
|
|
65
|
+
"jsonpath": "^1.1.1",
|
|
66
|
+
"lodash": "^4.17.21",
|
|
67
|
+
"react": ">=18",
|
|
68
|
+
"react-bootstrap": "^2.10.9",
|
|
69
|
+
"react-chartjs-2": "^5.3.0",
|
|
70
|
+
"react-dom": ">=18"
|
|
71
|
+
}
|
|
72
|
+
}
|