Skip to content

Commit d5153d0

Browse files
committed
Add support for movable array items
1 parent 1f5ba43 commit d5153d0

File tree

6 files changed

+152
-40
lines changed

6 files changed

+152
-40
lines changed

dev/index.html

Lines changed: 42 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -69,34 +69,59 @@
6969
margin-top: 15px;
7070
margin-bottom: 0;
7171
}
72-
.rjf-remove-button {
73-
color: #ff4444;
74-
background-color: #ffeeee;
72+
.rjf-form-row-controls {
7573
position: absolute;
76-
top: 0.3rem;
7774
right: 0.3rem;
78-
border-radius: 100%;
75+
top: 0.3rem;
76+
z-index: 2;
77+
border-radius: 10rem; /* same as child button */
78+
}
79+
.rjf-form-row > .rjf-form-row-controls {
80+
top: 50%;
81+
transform: translateY(-50%);
82+
}
83+
.rjf-form-row-controls:hover + .rjf-form-row-inner,
84+
.rjf-form-row-controls:hover + .rjf-form-group {
85+
background-color: #fffedd;
86+
}
87+
.rjf-form-row-controls button {
88+
position: relative;
89+
color: #333;
90+
background-color: #f3f3f3;
7991
padding: 0.3rem 0.58rem;
92+
margin-left: -1px;
8093
text-align: center;
8194
line-height: 1;
82-
border: 1px solid #ffcccc;
83-
font-size: 1.3rem;
95+
border: 1px solid #ccc;
96+
border-radius: 10rem;
97+
font-size: 1.2rem;
8498
font-weight: bold;
8599
cursor: pointer;
86-
transition: background-color 0.12s ease-in-out, border-color 0.12s ease-in-out;
87-
z-index: 2;
100+
transition: background-color 0.12s ease-in-out,
101+
border-color 0.12s ease-in-out,
102+
color 0.12s ease-in-out;
88103
}
89-
.rjf-form-row > .rjf-remove-button {
90-
top: 50%;
91-
transform: translateY(-50%);
104+
.rjf-form-row-controls > button:not(:last-child) {
105+
border-top-right-radius: 0;
106+
border-bottom-right-radius: 0;
92107
}
93-
.rjf-remove-button:hover {
108+
.rjf-form-row-controls > button:not(:first-child) {
109+
border-top-left-radius: 0;
110+
border-bottom-left-radius: 0;
111+
}
112+
.rjf-form-row-controls button:hover {
94113
background-color: #fff;
95-
border-color: #ff4444;
114+
border-color: #333;
115+
z-index: 1;
96116
}
97-
.rjf-remove-button:hover + .rjf-form-row-inner,
98-
.rjf-remove-button:hover + .rjf-form-group {
99-
background-color: #fff3f3;
117+
.rjf-form-row-controls .rjf-remove-button {
118+
color: #ff4444;
119+
background-color: #ffeeee;
120+
border-color: #ffcccc;
121+
}
122+
.rjf-form-row-controls .rjf-remove-button:hover {
123+
color: #ff4444;
124+
border-color: #ff4444;
100125
}
101126
.rjf-form-text {
102127
display: block;

src/components/buttons.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,16 @@ export default function Button({className, ...props}) {
22
if (!className)
33
className = '';
44

5-
className = 'rjf-' + className + '-button';
5+
let classes = className.split(' ');
6+
7+
className = '';
8+
for (let i = 0; i < classes.length; i++) {
9+
className = className + 'rjf-' + classes[i] + '-button ';
10+
}
11+
612
return (
713
<button
8-
className={className}
14+
className={className.trim()}
915
type="button"
1016
{...props}
1117
>

src/components/containers.js

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,44 @@ export function GroupTitle(props) {
1111
}
1212

1313

14-
export function FormRow(props) {
14+
export function FormRowControls(props) {
1515
return (
16-
<div className="rjf-form-row">
16+
<div className="rjf-form-row-controls">
17+
{props.onMoveUp &&
18+
<Button
19+
className="move-up"
20+
onClick={props.onMoveUp}
21+
title="Move up"
22+
>
23+
<span>&uarr;</span>
24+
</Button>
25+
}
26+
{props.onMoveDown &&
27+
<Button
28+
className="move-down"
29+
onClick={props.onMoveDown}
30+
title="Move down"
31+
>
32+
<span>&darr;</span>
33+
</Button>
34+
}
1735
{props.onRemove &&
1836
<Button
1937
className="remove"
20-
onClick={(e) => props.onRemove(name)}
38+
onClick={props.onRemove}
2139
title="Remove"
2240
>
2341
<span>&times;</span>
2442
</Button>
2543
}
44+
</div>
45+
);
46+
}
47+
48+
export function FormRow(props) {
49+
return (
50+
<div className="rjf-form-row">
51+
<FormRowControls {...props} />
2652
<div className="rjf-form-row-inner">
2753
{props.children}
2854
</div>

src/components/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import Button from './buttons';
22
import {FormInput, FormCheckInput, FormRadioInput, FormSelectInput, FormFileInput,
33
FormTextareaInput} from './form';
4-
import {FormRow, FormGroup} from './containers';
4+
import {FormRow, FormGroup, FormRowControls} from './containers';
55
import Loader from './loaders';
66

77
export {
88
Button,
99
FormInput, FormCheckInput, FormRadioInput, FormSelectInput, FormFileInput,
1010
FormTextareaInput,
11-
FormRow, FormGroup,
11+
FormRow, FormGroup, FormRowControls,
1212
Loader,
1313
};

src/form.js

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export default class Form extends React.Component {
9595
onChange: this.handleChange,
9696
onAdd: this.addFieldset,
9797
onRemove: this.removeFieldset,
98+
onMove: this.moveFieldset,
9899
level: 0
99100
};
100101

@@ -162,7 +163,7 @@ export default class Form extends React.Component {
162163
removeDataUsingCoords(coords, data[coord]);
163164
} else {
164165
if (Array.isArray(data))
165-
data = data.splice(coord, 1); // in-place mutation
166+
data.splice(coord, 1); // in-place mutation
166167
else
167168
delete data[coord];
168169
}
@@ -176,6 +177,47 @@ export default class Form extends React.Component {
176177
});
177178
}
178179

180+
moveFieldset = (oldCoords, newCoords) => {
181+
oldCoords = oldCoords.split("-");
182+
oldCoords.shift();
183+
184+
newCoords = newCoords.split("-");
185+
newCoords.shift();
186+
187+
this.setState((state) => {
188+
function moveDataUsingCoords(oldCoords, newCoords, data) {
189+
let oldCoord = oldCoords.shift();
190+
191+
if (!isNaN(Number(oldCoord)))
192+
oldCoord = Number(oldCoord);
193+
194+
if (oldCoords.length) {
195+
moveDataUsingCoords(oldCoords, newCoords, data[oldCoord]);
196+
} else {
197+
if (Array.isArray(data)) {
198+
/* Using newCoords allows us to move items from
199+
one array to another.
200+
However, for now, we're only moving items in a
201+
single array.
202+
*/
203+
let newCoord = newCoords[newCoords.length - 1];
204+
205+
let item = data[oldCoord];
206+
207+
data.splice(oldCoord, 1);
208+
data.splice(newCoord, 0, item);
209+
}
210+
}
211+
}
212+
213+
let _data = JSON.parse(JSON.stringify(state.data));
214+
215+
moveDataUsingCoords(oldCoords, newCoords, _data);
216+
217+
return {data: _data};
218+
});
219+
}
220+
179221
render() {
180222
return (
181223
<div className="rjf-form-wrapper">

src/ui.js

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {getBlankData} from './data';
22
import {Button, FormInput, FormCheckInput, FormRadioInput, FormSelectInput,
3-
FormFileInput, FormRow, FormGroup, FormTextareaInput} from './components';
3+
FormFileInput, FormRow, FormGroup, FormRowControls, FormTextareaInput} from './components';
44
import {getVerboseName} from './util';
55

66

@@ -105,12 +105,17 @@ function FormField(props) {
105105

106106

107107
export function getStringFormRow(args) {
108-
let {data, schema, name, onChange, onRemove, removable, onEdit, editable} = args;
108+
let {
109+
data, schema, name, onChange, onRemove, removable, onEdit, editable,
110+
onMoveUp, onMoveDown
111+
} = args;
109112

110113
return (
111114
<FormRow
112115
key={name}
113116
onRemove={removable ? (e) => onRemove(name) : null}
117+
onMoveUp={onMoveUp}
118+
onMoveDown={onMoveDown}
114119
>
115120
<FormField
116121
data={data}
@@ -125,7 +130,7 @@ export function getStringFormRow(args) {
125130
}
126131

127132
export function getArrayFormRow(args) {
128-
let {data, schema, name, onChange, onAdd, onRemove, level} = args;
133+
let {data, schema, name, onChange, onAdd, onRemove, onMove, level} = args;
129134

130135
let rows = [];
131136
let groups = [];
@@ -153,13 +158,24 @@ export function getArrayFormRow(args) {
153158
onAdd: onAdd,
154159
onRemove: onRemove,
155160
level: level + 1,
156-
removable: removable
161+
removable: removable,
162+
onMove: onMove,
157163
};
158164

159165
for (let i = 0; i < data.length; i++) {
160166
nextArgs.data = data[i];
161167
nextArgs.name = name + '-' + i;
162168

169+
if (i === 0)
170+
nextArgs.onMoveUp = null;
171+
else
172+
nextArgs.onMoveUp = (e) => onMove(name + '-' + i, name + '-' + (i - 1));
173+
174+
if (i === data.length - 1)
175+
nextArgs.onMoveDown = null;
176+
else
177+
nextArgs.onMoveDown = (e) => onMove(name + '-' + i, name + '-' + (i + 1));;
178+
163179
if (type === 'array') {
164180
groups.push(getArrayFormRow(nextArgs));
165181
} else if (type === 'object') {
@@ -193,15 +209,11 @@ export function getArrayFormRow(args) {
193209
{groupTitle}
194210
{groups.map((i, index) => (
195211
<div className="rjf-form-group-wrapper" key={'group_wrapper_' + name + '_' + index}>
196-
{removable &&
197-
<Button
198-
className="remove"
199-
onClick={(e) => onRemove(name + '-' + index)}
200-
title="Remove"
201-
>
202-
<span>&times;</span>
203-
</Button>
204-
}
212+
<FormRowControls
213+
onRemove={removable ? (e) => onRemove(name + '-' + index) : null}
214+
onMoveUp={index > 0 ? (e) => onMove(name + '-' + index, name + '-' + (index - 1)) : null}
215+
onMoveDown={index < groups.length - 1 ? (e) => onMove(name + '-' + index, name + '-' + (index + 1)) : null}
216+
/>
205217
{i}
206218
</div>
207219
)
@@ -224,7 +236,7 @@ export function getArrayFormRow(args) {
224236

225237

226238
export function getObjectFormRow(args) {
227-
let {data, schema, name, onChange, onAdd, onRemove, level} = args;
239+
let {data, schema, name, onChange, onAdd, onRemove, level, onMove} = args;
228240

229241
let rows = [];
230242

@@ -263,7 +275,8 @@ export function getObjectFormRow(args) {
263275
onAdd: onAdd,
264276
onRemove: onRemove,
265277
level: level + 1,
266-
removable: removable
278+
removable: removable,
279+
onMove: onMove
267280
};
268281

269282
if (type === 'array') {

0 commit comments

Comments
 (0)