@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.
Files changed (66) hide show
  1. package/README.md +83 -0
  2. package/dist/reactive-json.css +5 -0
  3. package/dist/reactive-json.js +56303 -0
  4. package/dist/reactive-json.umd.cjs +382 -0
  5. package/lib/component/action/HashChangeListener.jsx +66 -0
  6. package/lib/component/action/Hide.jsx +14 -0
  7. package/lib/component/action/MessageListener.jsx +62 -0
  8. package/lib/component/action/Popover.jsx +53 -0
  9. package/lib/component/action/ReactOnEvent.jsx +118 -0
  10. package/lib/component/action/Redirect.jsx +26 -0
  11. package/lib/component/action/Tooltip.jsx +27 -0
  12. package/lib/component/action/VisuallyHide.jsx +15 -0
  13. package/lib/component/element/chart/BarChart.jsx +40 -0
  14. package/lib/component/element/chart/DoughnutChart.jsx +32 -0
  15. package/lib/component/element/chart/LineChart.jsx +40 -0
  16. package/lib/component/element/chart/PolarAreaChart.jsx +32 -0
  17. package/lib/component/element/form/CheckBoxField.jsx +215 -0
  18. package/lib/component/element/form/DateField.jsx +42 -0
  19. package/lib/component/element/form/NumberField.jsx +29 -0
  20. package/lib/component/element/form/SelectField.jsx +130 -0
  21. package/lib/component/element/form/TextAreaField.jsx +48 -0
  22. package/lib/component/element/form/TextField.jsx +65 -0
  23. package/lib/component/element/form/formElementsCommon.jsx +54 -0
  24. package/lib/component/element/html/AccordionItem.jsx +42 -0
  25. package/lib/component/element/html/FolderSortableTree.jsx +307 -0
  26. package/lib/component/element/html/FormatNumeral.jsx +118 -0
  27. package/lib/component/element/html/Html.jsx +107 -0
  28. package/lib/component/element/html/LabelFromValue.jsx +89 -0
  29. package/lib/component/element/html/Modal.jsx +77 -0
  30. package/lib/component/element/html/ModalForm.jsx +30 -0
  31. package/lib/component/element/html/Paragraph.jsx +10 -0
  32. package/lib/component/element/html/PreformattedMarkup.jsx +54 -0
  33. package/lib/component/element/html/SortableTreeItemCollapseButton.jsx +20 -0
  34. package/lib/component/element/html/Tabs.jsx +55 -0
  35. package/lib/component/element/special/BootstrapElement.jsx +32 -0
  36. package/lib/component/element/special/Count.jsx +46 -0
  37. package/lib/component/element/special/DataFilter.jsx +156 -0
  38. package/lib/component/element/special/DelayedActions.jsx +119 -0
  39. package/lib/component/element/special/PageControls.jsx +19 -0
  40. package/lib/component/element/special/Phantom.jsx +25 -0
  41. package/lib/component/element/special/Switch.jsx +131 -0
  42. package/lib/component/hook/usePagination.jsx +184 -0
  43. package/lib/component/reaction/addData.jsx +23 -0
  44. package/lib/component/reaction/fetchData.jsx +83 -0
  45. package/lib/component/reaction/moveData.jsx +52 -0
  46. package/lib/component/reaction/postMessage.jsx +43 -0
  47. package/lib/component/reaction/redirectNow.jsx +17 -0
  48. package/lib/component/reaction/removeData.jsx +48 -0
  49. package/lib/component/reaction/setClipboardData.jsx +20 -0
  50. package/lib/component/reaction/setData.jsx +23 -0
  51. package/lib/component/reaction/submitData.jsx +136 -0
  52. package/lib/component/reaction/triggerEvent.jsx +62 -0
  53. package/lib/component/utility/formatString.jsx +59 -0
  54. package/lib/engine/Actions.jsx +392 -0
  55. package/lib/engine/EventDispatcherContext.jsx +16 -0
  56. package/lib/engine/EventDispatcherProvider.jsx +80 -0
  57. package/lib/engine/GlobalDataContext.jsx +13 -0
  58. package/lib/engine/GlobalDataContextProvider.jsx +33 -0
  59. package/lib/engine/PaginationContext.jsx +10 -0
  60. package/lib/engine/PaginationProvider.jsx +61 -0
  61. package/lib/engine/ReactiveJsonRoot.jsx +315 -0
  62. package/lib/engine/TemplateContext.jsx +13 -0
  63. package/lib/engine/TemplateSystem.jsx +302 -0
  64. package/lib/engine/View.jsx +240 -0
  65. package/lib/main.jsx +41 -0
  66. package/package.json +72 -0
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import Button from 'react-bootstrap/Button';
3
+ import Modal from 'react-bootstrap/Modal';
4
+ import View from "../../../engine/View";
5
+
6
+ function ModalForm({show, path, handleClose, handleSave, form, currentData}) {
7
+
8
+ return (
9
+ <>
10
+ <Modal show={show} onHide={handleClose}>
11
+ <Modal.Header closeButton>
12
+ <Modal.Title>Modal heading</Modal.Title>
13
+ </Modal.Header>
14
+ <Modal.Body>
15
+ <View props={form} currentData={currentData} path={path}/>
16
+ </Modal.Body>
17
+ <Modal.Footer>
18
+ <Button variant="secondary" onClick={handleClose}>
19
+ Close
20
+ </Button>
21
+ <Button variant="primary" onClick={handleSave}>
22
+ Save Changes
23
+ </Button>
24
+ </Modal.Footer>
25
+ </Modal>
26
+ </>
27
+ )
28
+ }
29
+
30
+ export default ModalForm;
@@ -0,0 +1,10 @@
1
+ import React from 'react';
2
+
3
+ const Paragraph = ({props, currentData}) => {
4
+ return (
5
+ <>
6
+ {currentData}
7
+ </>
8
+ )
9
+ }
10
+ export default Paragraph;
@@ -0,0 +1,54 @@
1
+ import ActionDependant from "../../../engine/Actions";
2
+ import GlobalDataContext from "../../../engine/GlobalDataContext";
3
+ import TemplateContext from "../../../engine/TemplateContext";
4
+ import {evaluateTemplateValue} from "../../../engine/TemplateSystem";
5
+ import parse from 'html-react-parser';
6
+ import React, {useContext} from 'react';
7
+
8
+ /**
9
+ * List of tags that are allowed by default.
10
+ *
11
+ * @type {string[]}
12
+ */
13
+ const defaultHtmlTagAllowList = [
14
+ "abbr", "acronym", "b", "blockquote", "br", "caption", "code", "div", "em", "h1", "h2", "h3", "h4", "h5", "h6",
15
+ "i", "li", "ol", "p", "span", "sup", "strong", "table", "tbody",
16
+ "td", "tfoot", "th", "thead", "tr", "ul",
17
+ ];
18
+
19
+ /**
20
+ * Preformatted markup component that takes content and inserts it as HTML.
21
+ */
22
+ const PreformattedMarkup = ({props}) => {
23
+ const templateContext = useContext(TemplateContext);
24
+ const globalDataContext = useContext(GlobalDataContext);
25
+
26
+ const html = evaluateTemplateValue({valueToEvaluate: props.content, templateContext, globalDataContext});
27
+
28
+ // Allow the base list to be overridden. Useful to disallow all tags, by supplying an empty array.
29
+ const htmlTagAllowList = Array.isArray(props.htmlTagAllowList) ? props.htmlTagAllowList : defaultHtmlTagAllowList;
30
+
31
+ // Allow the list to be extended with the additionalAllowedTags component property.
32
+ const completeTagAllowList = [...htmlTagAllowList, ...(props.additionalAllowedTags ?? [])]
33
+
34
+ return (
35
+ <ActionDependant {...props}>
36
+ {parse(html, {
37
+ replace(domNode) {
38
+ /*
39
+ * Filter tags to remove any risk about rendering dangerous markup.
40
+ * Attributes (e.g. onclick) do not need to be filtered because they are not parsed as code:
41
+ * https://github.com/remarkablemark/html-react-parser/issues/73#issuecomment-426119592
42
+ */
43
+ if (domNode.type === "tag" && completeTagAllowList.indexOf(domNode.name) < 0) {
44
+ // Not an allowed tag.
45
+ // Replace by a fragment which will effectively replace by nothing.
46
+ return <></>;
47
+ }
48
+ }
49
+ })}
50
+ </ActionDependant>
51
+ )
52
+ }
53
+
54
+ export default PreformattedMarkup;
@@ -0,0 +1,20 @@
1
+ import ActionDependant from "../../../engine/Actions";
2
+ import TemplateContext from "../../../engine/TemplateContext";
3
+ import {useContext} from "react";
4
+
5
+ /**
6
+ * This is the collapse button for the sortable tree.
7
+ *
8
+ * It's used when SortableTree is used with a manualDrag option.
9
+ */
10
+ const SortableTreeItemCollapseButton = ({props}) => {
11
+ const templateContext = useContext(TemplateContext);
12
+
13
+ return (
14
+ <ActionDependant {...props}>
15
+ {templateContext.sortableTreeData._treeAddCollapseButton?.()}
16
+ </ActionDependant>
17
+ )
18
+ };
19
+
20
+ export default SortableTreeItemCollapseButton;
@@ -0,0 +1,55 @@
1
+ import ActionDependant from "../../../engine/Actions";
2
+ import GlobalDataContext from "../../../engine/GlobalDataContext";
3
+ import TemplateContext from "../../../engine/TemplateContext";
4
+ import {
5
+ evaluateAttributes, useEvaluatedAttributes
6
+ } from "../../../engine/TemplateSystem";
7
+ import View from "../../../engine/View";
8
+ import {useContext} from "react";
9
+ import BsTab from 'react-bootstrap/Tab';
10
+ import BsTabs from 'react-bootstrap/Tabs';
11
+
12
+ /**
13
+ * Tabs component using the simple Tabs component from react-bootstrap.
14
+ *
15
+ * The react-bootstrap's Tabs component is special as it requires a very
16
+ * specific structure with <Tabs> directly underneath.
17
+ *
18
+ * @param currentData
19
+ * @param path
20
+ * @param props
21
+ * @returns {JSX.Element}
22
+ * @constructor
23
+ */
24
+ const Tabs = ({currentData, path, props}) => {
25
+ const globalDataContext = useContext(GlobalDataContext);
26
+ const templateContext = useContext(TemplateContext);
27
+
28
+ const evaluatedAttrs = useEvaluatedAttributes(props.attributes);
29
+
30
+ return (
31
+ <ActionDependant {...props}>
32
+ <BsTabs {...evaluatedAttrs}>
33
+ {Array.isArray(props.tabs) && props.tabs.map((item, index) => {
34
+ const tabAttributes = evaluateAttributes({
35
+ attrs: item.attributes,
36
+ globalDataContext,
37
+ templateContext,
38
+ options: {normalizeBeforeEvaluation: true}
39
+ });
40
+
41
+ return <BsTab {...tabAttributes} key={index}>
42
+ <View
43
+ currentData={currentData?.[index]?.content ?? undefined}
44
+ datafield={"content"}
45
+ path={(path ?? "") + "." + index + ".content"}
46
+ props={item?.content}/>
47
+ </BsTab>
48
+ })}
49
+
50
+ </BsTabs>
51
+ </ActionDependant>
52
+ );
53
+ };
54
+
55
+ export default Tabs;
@@ -0,0 +1,32 @@
1
+ import ActionDependant from "../../../engine/Actions";
2
+ import View from "../../../engine/View";
3
+ import {useEvaluatedAttributes} from "../../../engine/TemplateSystem";
4
+
5
+ /**
6
+ * Wraps a React Bootstrap component.
7
+ */
8
+ function BootstrapElement({props, currentData, path, bsComponent}) {
9
+ const attributes = useEvaluatedAttributes(props.attributes);
10
+
11
+ if (props.attributes?.["data-visually-hidden"]) {debugger;}
12
+
13
+ if (!bsComponent) {
14
+ return null;
15
+ }
16
+
17
+ const BsElement = bsComponent;
18
+
19
+ return <ActionDependant {...props}>
20
+ <BsElement {...attributes}>
21
+ {props.content &&
22
+ (<View
23
+ currentData={currentData.content ?? undefined}
24
+ datafield={"content"}
25
+ path={path + ".content"}
26
+ props={props.content}
27
+ />)}
28
+ </BsElement>
29
+ </ActionDependant>;
30
+ }
31
+
32
+ export default BootstrapElement;
@@ -0,0 +1,46 @@
1
+ import JSONPath from "jsonpath";
2
+ import {useContext} from "react";
3
+ import GlobalDataContext from "../../../engine/GlobalDataContext";
4
+ import TemplateContext from "../../../engine/TemplateContext";
5
+ import {maybeFormatString} from "../../utility/formatString";
6
+
7
+ /**
8
+ * Returns a count for something to count.
9
+ *
10
+ * @param props Component build data.
11
+ *
12
+ * @returns {null|*}
13
+ *
14
+ * @constructor
15
+ */
16
+ const Count = ({props}) => {
17
+ const globalDataContext = useContext(GlobalDataContext);
18
+ const templateContext = useContext(TemplateContext);
19
+
20
+ const {
21
+ context = "global",
22
+ jsonPathPattern: _jsonPathPattern,
23
+ } = props;
24
+
25
+ const _selectedContext = context === "template" ? TemplateContext : GlobalDataContext;
26
+ const selectedContext = useContext(_selectedContext);
27
+
28
+ if (!_jsonPathPattern) {
29
+ return null;
30
+ }
31
+
32
+ const jsonPathPattern = maybeFormatString({
33
+ templateContexts: {
34
+ globalDataContext,
35
+ templateContext
36
+ }
37
+ }, _jsonPathPattern);
38
+
39
+ const selectedContextData = context === "root" ? selectedContext.getRootContext().templateData : selectedContext.templateData;
40
+
41
+ const result = JSONPath.query(selectedContextData, jsonPathPattern);
42
+
43
+ return result.length;
44
+ };
45
+
46
+ export default Count;
@@ -0,0 +1,156 @@
1
+ import {useContext} from "react";
2
+ import {isValid} from "../../../engine/Actions";
3
+ import GlobalDataContext from "../../../engine/GlobalDataContext";
4
+ import TemplateContext from "../../../engine/TemplateContext";
5
+ import View from "../../../engine/View";
6
+
7
+ /**
8
+ * Use DataFilter to filter data from the global or template data.
9
+ *
10
+ * @param args Component build data.
11
+ *
12
+ * @returns {JSX.Element}
13
+ *
14
+ * @constructor
15
+ */
16
+ const DataFilter = (args) => {
17
+ const globalDataContext = useContext(GlobalDataContext);
18
+ const templateContext = useContext(TemplateContext);
19
+ const templateContexts = {globalDataContext, templateContext};
20
+
21
+ const {
22
+ contextToFilter = "global",
23
+ filters = [],
24
+ } = args.props;
25
+
26
+ const filterData = (data) => {
27
+ if (!data) {
28
+ return true;
29
+ }
30
+
31
+ for (const filter of filters) {
32
+ const subjectsWithProperty = filter?.subjectsWithProperty ?? undefined;
33
+
34
+ if (!subjectsWithProperty) {
35
+ // Ignore this filter.
36
+ continue;
37
+ }
38
+
39
+ if (!data.hasOwnProperty(subjectsWithProperty)) {
40
+ continue;
41
+ }
42
+
43
+ const additionalConditionHandlers = new Map(
44
+ [
45
+ [
46
+ "whenFilterableData",
47
+ ({condition}) => {
48
+ // Walk through the data.
49
+ const path = condition["whenFilterableData"];
50
+ const pathArray = path.split('.');
51
+ let current = data;
52
+
53
+ for (const segment of pathArray) {
54
+ const index = parseInt(segment);
55
+ current = current[isNaN(index) ? segment : index];
56
+ if (current === undefined) {
57
+ return undefined;
58
+ }
59
+ }
60
+
61
+ return current;
62
+ }
63
+ ]
64
+ ]
65
+ );
66
+
67
+ // The item may be filtered out by this filter definition.
68
+ // Now, check the activation conditions.
69
+ if (!isValid(filter, templateContexts, additionalConditionHandlers)) {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ // No filter for this data, keep it.
75
+ return true;
76
+ };
77
+
78
+
79
+ switch (contextToFilter) {
80
+ case "template":
81
+ templateContext.templateData = cloneAndFilter(templateContext.templateData, filterData);
82
+ templateContext.templatePath = args.path;
83
+
84
+ return <TemplateContext.Provider value={templateContext}>
85
+ <View
86
+ props={args.props.content}
87
+ path={args.path + ".content"}
88
+ datafield={"content"}
89
+ currentData={args.currentData?.["content"] ?? undefined}/>
90
+ </TemplateContext.Provider>;
91
+
92
+ case "global":
93
+ default:
94
+ // We rewrite the template data.
95
+ globalDataContext.templateData = cloneAndFilter(globalDataContext.templateData, filterData);
96
+
97
+ return <GlobalDataContext.Provider value={globalDataContext}>
98
+ <View
99
+ props={args.props.content}
100
+ path={args.path + ".content"}
101
+ datafield={"content"}
102
+ currentData={args.currentData?.["content"] ?? undefined}/>
103
+ </GlobalDataContext.Provider>;
104
+ }
105
+ };
106
+
107
+ /**
108
+ * Clones the data to an object only structure.
109
+ *
110
+ * It means that arrays will be converted to objects,
111
+ * with the array indices converted to property keys.
112
+ * This is necessary because we want to preserve the
113
+ * paths of the items that are still appearing after
114
+ * the filtering process, so that they can call
115
+ * updateData as usual.
116
+ *
117
+ * @param data
118
+ * @param {function} filterData
119
+ *
120
+ * @returns {{}|*}
121
+ */
122
+ const cloneAndFilter = (data, filterData) => {
123
+ if (Array.isArray(data)) {
124
+ const obj = {};
125
+
126
+ data.forEach((item, index) => {
127
+ if (!filterData(item)) {
128
+ // Do not keep this item.
129
+ return;
130
+ }
131
+
132
+ obj[index] = cloneAndFilter(item, filterData);
133
+ });
134
+
135
+ return obj;
136
+ } else if (typeof data === 'object' && data !== null) {
137
+ const obj = {};
138
+
139
+ for (const key in data) {
140
+ if (data.hasOwnProperty(key)) {
141
+ // We only work with own properties, not inherited ones.
142
+ if (!filterData(data[key])) {
143
+ // Do not keep this item.
144
+ continue;
145
+ }
146
+
147
+ obj[key] = cloneAndFilter(data[key], filterData);
148
+ }
149
+ }
150
+ return obj;
151
+ } else {
152
+ return data;
153
+ }
154
+ }
155
+
156
+ export default DataFilter;
@@ -0,0 +1,119 @@
1
+ import ActionDependant, {isValid} from "../../../engine/Actions";
2
+ import GlobalDataContext from "../../../engine/GlobalDataContext";
3
+ import TemplateContext from "../../../engine/TemplateContext";
4
+ import View from "../../../engine/View";
5
+ import {reactionFunctions} from "../../action/ReactOnEvent";
6
+ import {useContext, useEffect} from "react";
7
+
8
+ /**
9
+ * Provides a way to execute actions after a delay, at intervals, etc.
10
+ *
11
+ * @param {{}} props Build data.
12
+ * @param currentData Current data.
13
+ * @param path Element path.
14
+ *
15
+ * @constructor
16
+ */
17
+ const DelayedActions = ({props, currentData, path}) => {
18
+ const globalDataContext = useContext(GlobalDataContext);
19
+ const templateContext = useContext(TemplateContext);
20
+
21
+ const delayedActions = Array.isArray(props.delayedActions) ? props.delayedActions : [];
22
+ const templateContexts = {globalDataContext, templateContext};
23
+
24
+ useEffect(() => {
25
+ if (!props.interval) {
26
+ return;
27
+ }
28
+
29
+ const interval = setInterval(() => {
30
+ const reactionFunctionsToExecute = getReactionFunctionsToExecute(delayedActions, templateContexts);
31
+
32
+ // Execute the reaction functions.
33
+ // They are supposed to be validated for execution by getReactionFunctionsToExecute.
34
+ // The events (on change, on click, etc.) are ignored because
35
+ // it does not make sense to have to wait for a delay AND a specific event.
36
+ // The time is already an event.
37
+ for (let reactionFunctionPropsIndex = 0; reactionFunctionPropsIndex < reactionFunctionsToExecute.length; ++reactionFunctionPropsIndex) {
38
+ const singleReactionFunctionProps = reactionFunctionsToExecute[reactionFunctionPropsIndex];
39
+
40
+ if (!singleReactionFunctionProps) {
41
+ // Empty definition.
42
+ continue;
43
+ }
44
+
45
+ const reactionFunction = singleReactionFunctionProps.what && (reactionFunctions[singleReactionFunctionProps.what] ?? null);
46
+
47
+ if (!reactionFunction) {
48
+ continue;
49
+ }
50
+
51
+ // Call the reaction function with the props and context data.
52
+ // This differs from the ReactOnEvent implementation by not including event data,
53
+ // because we did not trigger an event.
54
+ reactionFunction({args: singleReactionFunctionProps, globalDataContext, templateContext});
55
+ }
56
+
57
+ if (props.once) {
58
+ clearInterval(interval);
59
+ }
60
+ }, props.interval);
61
+
62
+ return () => clearInterval(interval);
63
+ }, [globalDataContext, templateContext]);
64
+
65
+ return <ActionDependant {...props}>
66
+ {props.content && <View
67
+ props={props.content}
68
+ currentData={currentData?.content ?? undefined}
69
+ datafield={"content"}
70
+ path={path + ".content"}/>}
71
+ </ActionDependant>;
72
+ };
73
+
74
+
75
+ /**
76
+ * Gets the reaction functions to execute.
77
+ *
78
+ * This is a specific adaptation of getActionsToExecute from the Actions core functionality.
79
+ *
80
+ * @param {Array} actions
81
+ * @param {object} templateContexts
82
+ * @returns {*[]} The list of reaction function properties. The structure is simpler than getActionsToExecute.
83
+ */
84
+ export const getReactionFunctionsToExecute = (actions, templateContexts) => {
85
+ const result = [];
86
+
87
+ if (!Array.isArray(actions)) {
88
+ // Not a supported actions structure.
89
+ // Dev note: we may also allow objects in the future, to allow specific overrides.
90
+ return result;
91
+ }
92
+
93
+ for (const [, item] of actions.entries()) {
94
+ const what = item?.what ?? undefined;
95
+
96
+ if (!what) {
97
+ continue;
98
+ }
99
+
100
+ const reactionFunction = reactionFunctions[what] ?? undefined;
101
+
102
+ if (!reactionFunction) {
103
+ // The component is unknown or not registered,
104
+ // and it's not a reaction function.
105
+ continue;
106
+ }
107
+
108
+ // This is a reaction function.
109
+ if (!isValid(item, templateContexts)) {
110
+ continue;
111
+ }
112
+
113
+ result.push(item);
114
+ }
115
+
116
+ return result;
117
+ };
118
+
119
+ export default DelayedActions;
@@ -0,0 +1,19 @@
1
+ import {useContext} from "react";
2
+ import PaginationContext from "../../../engine/PaginationContext";
3
+
4
+ /**
5
+ * Displays the PageControls found in the PaginationContext, if any.
6
+ *
7
+ * @returns {JSX.Element|null}
8
+ *
9
+ * @constructor
10
+ */
11
+ const PageControls = () => {
12
+ const {pagination} = useContext(PaginationContext);
13
+
14
+ return pagination.PageControls
15
+ ? <pagination.PageControls/>
16
+ : null;
17
+ };
18
+
19
+ export default PageControls;
@@ -0,0 +1,25 @@
1
+ import ActionDependant from "../../../engine/Actions";
2
+ import View from "../../../engine/View";
3
+
4
+ /**
5
+ * Phantom element without DOM output.
6
+ *
7
+ * Use this if you want actions without a DOM element.
8
+ *
9
+ * @param {{}} props Build data.
10
+ * @param currentData Current data.
11
+ * @param path Element path.
12
+ *
13
+ * @constructor
14
+ */
15
+ const Phantom = ({props, currentData, path}) => {
16
+ return <ActionDependant {...props}>
17
+ {props.content && <View
18
+ props={props.content}
19
+ currentData={currentData?.content ?? undefined}
20
+ datafield={"content"}
21
+ path={path + ".content"}/>}
22
+ </ActionDependant>;
23
+ };
24
+
25
+ export default Phantom;