Skip to content

Commit 3bc6437

Browse files
committed
make dnd optional
1 parent 6419ae1 commit 3bc6437

File tree

10 files changed

+369
-182
lines changed

10 files changed

+369
-182
lines changed

README.md

Lines changed: 29 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ Formule is a **powerful, user-friendly, extensible and mobile-friendly form buil
66

77
It originated from the need of a flexible tool for physicists at CERN to create their custom forms in the [CERN Analysis Preservation](https://github.com/cernanalysispreservation/analysispreservation.cern.ch) application (a process that was originally done by the CAP team who had to manually define the JSON schemas for every member experiment) in a zero-code fashion. This tool proved to be very useful for us to more easily scalate and expand, reaching a wider audience here at CERN. So, we thought it could also be useful for other people and decided to decouple it from CAP and release it as an open source library.
88

9-
>[!WARNING]
10-
>react-formule has just come out and is undergoing active development, so please feel free to share any issue you find with us and/or to contribute!
9+
> [!WARNING]
10+
> react-formule has just come out and is undergoing active development, so please feel free to share any issue you find with us and/or to contribute!
1111
1212
## :carousel_horse: How it looks like
1313

@@ -21,19 +21,20 @@ Formule consists of the following main components:
2121

2222
- **`FormuleContext`**: Formule components need to be wrapped by a FormuleContext. It also allows you to provide an antd theme and your own custom fields and widgets.
2323
- The form editor, which has been split into three different components that work together for more flexibility:
24-
- **`SelectOrEdit`** (or, separately, **`SelectFieldType`** and **`PropertyEditor`**): You can select fields to add to the form and customize their properties.
25-
- **`SchemaPreview`**: A tree view of the fields where you can rearrange or select fields to be edited.
26-
- **`FormPreview`**: A live, iteractive preview of the form.
24+
- **`SelectOrEdit`** (or, separately, **`SelectFieldType`** and **`PropertyEditor`**): You can select fields to add to the form and customize their properties.
25+
- **`SchemaPreview`**: A tree view of the fields where you can rearrange or select fields to be edited.
26+
- **`FormPreview`**: A live, iteractive preview of the form.
2727
- **`FormuleForm`**: You can use it to display a form (JSON Schema) generated by Formule.
2828

2929
It also exports the following functions:
3030

31-
- **`initFormuleSchema`**: Inits the JSONSchema, *needs* to be run on startup.
31+
- **`initFormuleSchema`**: Inits the JSONSchema, _needs_ to be run on startup.
3232
- **`getFormuleState`**: Formule has its own internal redux state. You can retrieve it at any moment if you so require for more advanced use cases. If you want to continuosly synchronize the Formule state in your app, you can pass a callback function to FormuleContext instead (see below), which will be called every time the form state changes.
3333

3434
### Field types
3535

3636
Formule includes a variety of predefined field types, grouped in three categories:
37+
3738
- **Simple fields**: `Text`, `Text area`, `Number`, `Checkbox`, `Switch`, `Radio`, `Select` and `Date` fields.
3839
- **Collections**:
3940
- `Object`: Use it of you want to group fields or to add several of them inside of a `List`.
@@ -45,23 +46,25 @@ Formule includes a variety of predefined field types, grouped in three categorie
4546

4647
You can freely remove some of these predefined fields and add your own custom fields and widgets following the JSON Schema specifications. More details below.
4748

48-
All of these items contain different settings that you can tinker with, separated into **Schema Settings** (*generally* affecting how the field *works*) and **UI Schema Settings** (*generally* affecting how the field *looks* like).
49+
All of these items contain different settings that you can tinker with, separated into **Schema Settings** (_generally_ affecting how the field _works_) and **UI Schema Settings** (_generally_ affecting how the field _looks_ like).
4950

5051
## :horse_racing: Setting it up
5152

5253
### Installation
54+
5355
```sh
5456
npm install react-formule
5557
# or
5658
yarn add react-formule
5759
```
5860

5961
### Basic setup
62+
6063
```jsx
61-
import {
62-
FormuleContext,
63-
SelectOrEdit,
64-
SchemaPreview,
64+
import {
65+
FormuleContext,
66+
SelectOrEdit,
67+
SchemaPreview,
6568
FormPreview,
6669
initFormuleSchema
6770
} from "react-formule";
@@ -75,7 +78,10 @@ const useEffect(() => initFormuleSchema(), []);
7578
</FormuleContext>
7679
```
7780

81+
If you want to disable the [DnD](https://github.com/react-dnd/react-dnd) functionality, you can pass `dnd={false}` to `FormuleContext`. This will enable an alternative, button-based method to add and move fields.
82+
7883
### Customizing and adding new field types
84+
7985
```jsx
8086
<FormuleContext theme={{token: {colorPrimary: "blue"}}} customFieldTypes={...} customFields={...} customWidgets={...}>
8187
// ...
@@ -86,26 +92,25 @@ If you use Formule to edit existing JSON schemas that include extra fields (e.g.
8692

8793
```jsx
8894
const transformSchema = (schema) => {
89-
// Remove properties...
90-
return transformedSchema
91-
}
95+
// Remove properties...
96+
return transformedSchema;
97+
};
9298

93-
<FormuleContext transformSchema={transformSchema}>
94-
// ...
95-
</FormuleContext>
99+
<FormuleContext transformSchema={transformSchema}>// ...</FormuleContext>;
96100
```
97101

98102
### Syncing Formule state
103+
99104
If you want to run some logic in your application every time the current Formule state changes in any way (e.g. to run some action every time a new field is added to the form) you can pass a function to be called back when that happens:
100105

101106
```jsx
102-
const handleFormuleStateChange = newState => {
103-
// Do something when the state changes
104-
}
107+
const handleFormuleStateChange = (newState) => {
108+
// Do something when the state changes
109+
};
105110

106111
<FormuleContext synchonizeState={handleFormuleStateChange}>
107-
// ...
108-
</FormuleContext>
112+
// ...
113+
</FormuleContext>;
109114
```
110115

111116
Alternatively, you can pull the current state on demand by calling `getFormuleState` at any moment.
@@ -114,4 +119,5 @@ Alternatively, you can pull the current state on demand by calling `getFormuleSt
114119
> For more examples, feel free to browse around the [CERN Analysis Preservation](https://github.com/cernanalysispreservation/analysispreservation.cern.ch) repository, where we use all the features mentioned above.
115120
116121
## :space_invader: Local demo & how to contribute
117-
You can also clone the repo and run `formule-demo` to play around. Follow the instructions in its [README](./formule-demo/README.md): it will explain how to install `react-formule` as a local dependency (with either `yarn link` or, better, `yalc`) so that you can modify Formule and test the changes live in your host app, which will be ideal if you want to troubleshoot or contribute to the project.
122+
123+
You can also clone the repo and run `formule-demo` to play around. Follow the instructions in its [README](./formule-demo/README.md): it will explain how to install `react-formule` as a local dependency (with either `yarn link` or, better, `yalc`) so that you can modify Formule and test the changes live in your host app, which will be ideal if you want to troubleshoot or contribute to the project.

formule-demo/src/App.tsx

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { initFormuleSchema } from "react-formule";
55
import { useEffect } from "react";
66
import { Row, Col } from "antd";
77

8-
import "./style.css"
8+
import "./style.css";
99

1010
const PRIMARY_COLOR = "#006996";
1111

@@ -15,16 +15,18 @@ function App() {
1515
}, []);
1616

1717
return (
18-
<FormuleContext theme={{
19-
token: {
20-
colorPrimary: PRIMARY_COLOR,
21-
colorLink: PRIMARY_COLOR,
22-
colorLinkHover: "#1a7fa3",
23-
borderRadius: 2,
24-
colorBgLayout: "#f0f2f5",
25-
fontFamily: "Titillium Web",
26-
},
27-
}}>
18+
<FormuleContext
19+
theme={{
20+
token: {
21+
colorPrimary: PRIMARY_COLOR,
22+
colorLink: PRIMARY_COLOR,
23+
colorLinkHover: "#1a7fa3",
24+
borderRadius: 2,
25+
colorBgLayout: "#f0f2f5",
26+
fontFamily: "Titillium Web",
27+
},
28+
}}
29+
>
2830
<Row style={{ height: "100%" }}>
2931
<Col
3032
xs={10}
@@ -33,6 +35,7 @@ function App() {
3335
overflowX: "hidden",
3436
height: "100%",
3537
display: "flex",
38+
padding: "0px 15px",
3639
}}
3740
className="tour-field-types"
3841
>
@@ -59,7 +62,6 @@ function App() {
5962
<FormPreview liveValidate={true} />
6063
</Col>
6164
</Row>
62-
6365
</FormuleContext>
6466
);
6567
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { Button, Modal } from "antd";
2+
import SelectFieldType from "./SelectFieldType";
3+
4+
const SelectFieldModal = ({ visible, setVisible, insertInPath }) => {
5+
return (
6+
<Modal
7+
destroyOnClose
8+
open={visible}
9+
onCancel={() => setVisible(false)}
10+
footer={
11+
<Button type="primary" onClick={() => setVisible(false)}>
12+
Ok
13+
</Button>
14+
}
15+
width={450}
16+
>
17+
<SelectFieldType insertInPath={insertInPath} />
18+
</Modal>
19+
);
20+
};
21+
22+
export default SelectFieldModal;

src/admin/components/SelectFieldType.jsx

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,49 @@
1-
import { Col, Collapse, Row, Space, Typography } from "antd";
1+
import { Button, Col, Collapse, Row, Space, Typography } from "antd";
22
import Draggable from "./Draggable";
33
import { useContext } from "react";
44
import CustomizationContext from "../../contexts/CustomizationContext";
5+
import PlusOutlined from "@ant-design/icons/PlusOutlined";
6+
import { useDispatch } from "react-redux";
7+
import { addByPath } from "../../store/schemaWizard";
58

6-
const SelectFieldType = () => {
7-
8-
const customizationContext = useContext(CustomizationContext)
9+
const SelectFieldType = ({ insertInPath }) => {
10+
const dispatch = useDispatch();
11+
12+
const customizationContext = useContext(CustomizationContext);
13+
14+
const DraggableOrNot = ({ index, type, objectKey, children }) => {
15+
if (customizationContext.dnd && !insertInPath) {
16+
return (
17+
<Draggable key={index} data={type} type={objectKey}>
18+
{children}
19+
</Draggable>
20+
);
21+
} else {
22+
return (
23+
<Row>
24+
<Col flex="auto">{children}</Col>
25+
<Col>
26+
<Button
27+
icon={<PlusOutlined style={{ fontSize: "14px" }} />}
28+
onClick={() =>
29+
dispatch(
30+
addByPath({
31+
path: insertInPath || { schema: [], uiSchema: [] },
32+
value: type.default,
33+
}),
34+
)
35+
}
36+
type="link"
37+
size="small"
38+
/>
39+
</Col>
40+
</Row>
41+
);
42+
}
43+
};
944

1045
return (
11-
<div style={{ width: "100%", padding: "0px 15px" }}>
46+
<div style={{ width: "100%" }}>
1247
<Typography.Title
1348
level={4}
1449
style={{ textAlign: "center", margin: "15px 0" }}
@@ -18,25 +53,27 @@ const SelectFieldType = () => {
1853
<Collapse
1954
defaultActiveKey={["simple", "collections"]}
2055
ghost
21-
items={Object.entries(customizationContext.allFieldTypes).map(([key, type]) => ({
22-
key: key,
23-
label: type.title,
24-
children: (
25-
<Row gutter={[16, 8]}>
26-
{Object.entries(type.fields).map(([key, type], index) => (
27-
<Col xs={22} xl={12} key={key} style={{ width: "100%" }}>
28-
<Draggable key={index} data={type} type={key}>
29-
<Space style={{ padding: "2px 5px" }}>
30-
{type.icon}
31-
{type.title}
32-
</Space>
33-
</Draggable>
34-
</Col>
35-
))}
36-
</Row>
37-
),
38-
className: type.className,
39-
}))}
56+
items={Object.entries(customizationContext.allFieldTypes).map(
57+
([key, type]) => ({
58+
key: key,
59+
label: type.title,
60+
children: (
61+
<Row gutter={[16, 8]}>
62+
{Object.entries(type.fields).map(([key, type], index) => (
63+
<Col xs={22} xl={12} key={key} style={{ width: "100%" }}>
64+
<DraggableOrNot index={index} type={type} objectKey={key}>
65+
<Space style={{ padding: "2px 5px" }}>
66+
{type.icon}
67+
{type.title}
68+
</Space>
69+
</DraggableOrNot>
70+
</Col>
71+
))}
72+
</Row>
73+
),
74+
className: type.className,
75+
}),
76+
)}
4077
/>
4178
</div>
4279
);

src/admin/formComponents/ArrayFieldTemplate.jsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
1-
import { useState } from "react";
1+
import { useContext, useState } from "react";
22
import PropTypes from "prop-types";
33
import SchemaTreeItem from "./SchemaTreeItem";
44
import Form from "../../forms/Form";
55
import ObjectFieldTemplate from "./ObjectFieldTemplate";
66
import FieldTemplate from "./FieldTemplate";
77
import { _validate } from "../utils";
88
import DropArea from "./DropArea";
9+
import CustomizationContext from "../../contexts/CustomizationContext";
910

10-
const ArrayFieldTemplate = props => {
11+
const ArrayFieldTemplate = (props) => {
1112
const [display, setDisplay] = useState(false);
1213

14+
const customizationContext = useContext(CustomizationContext);
15+
1316
let schemaPath = [];
1417
let uiSchemaPath = [];
1518
if (props.rawErrors) {
16-
let _rawErrors = props.rawErrors.filter(i => (i.schema ? i : false));
19+
let _rawErrors = props.rawErrors.filter((i) => (i.schema ? i : false));
1720
let { schema, uiSchema } = _rawErrors[0];
1821
schemaPath = schema;
1922
uiSchemaPath = uiSchema;
@@ -41,7 +44,8 @@ const ArrayFieldTemplate = props => {
4144

4245
{display && (
4346
<div style={{ marginLeft: "10px" }}>
44-
{Object.keys(props.schema.items).length == 0 ? (
47+
{Object.keys(props.schema.items).length == 0 &&
48+
customizationContext.dnd ? (
4549
<DropArea />
4650
) : (
4751
<Form
@@ -77,4 +81,4 @@ ArrayFieldTemplate.propTypes = {
7781
id: PropTypes.string,
7882
};
7983

80-
export default ArrayFieldTemplate
84+
export default ArrayFieldTemplate;

0 commit comments

Comments
 (0)