Skip to content

Commit 806373e

Browse files
committed
Fix #17: Add support for multiple choices
1 parent 4eb76ab commit 806373e

File tree

3 files changed

+179
-25
lines changed

3 files changed

+179
-25
lines changed

src/components/form.js

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,142 @@ export function FormSelectInput({label, help_text, error, value, options, ...pro
111111
);
112112
}
113113

114+
export class FormMultiSelectInput extends React.Component {
115+
constructor(props) {
116+
super(props);
117+
118+
this.state = {
119+
showOptions: false
120+
};
121+
122+
this.optionsContainer = React.createRef();
123+
this.input = React.createRef();
124+
}
125+
126+
handleChange = (e) => {
127+
let value = [...this.props.value];
128+
129+
if (e.target.checked) {
130+
value.push(e.target.value);
131+
} else {
132+
value = value.filter((item) => item !== e.target.value);
133+
}
134+
135+
let event = {
136+
target: {
137+
type: this.props.type,
138+
value: value,
139+
name: this.props.name
140+
}
141+
};
142+
143+
this.props.onChange(event);
144+
}
145+
146+
showOptions = (e) => {
147+
if (!this.state.showOptions)
148+
this.setState({showOptions: true});
149+
}
150+
151+
hideOptions = (e) => {
152+
this.setState({showOptions: false});
153+
}
154+
155+
toggleOptions = (e) => {
156+
this.setState((state) => ({showOptions: !state.showOptions}))
157+
}
158+
159+
render() {
160+
return (
161+
<div className="rjf-multiselect-field">
162+
<FormInput
163+
label={this.props.label}
164+
value={this.props.value.length ? this.props.value.length + ' selected' : 'Select...'}
165+
help_text={this.props.help_text}
166+
error={this.props.error}
167+
onClick={this.toggleOptions}
168+
readOnly={true}
169+
inputRef={this.input}
170+
className="rjf-multiselect-field-input"
171+
/>
172+
{this.state.showOptions &&
173+
<FormMultiSelectInputOptions
174+
options={this.props.options}
175+
value={this.props.value}
176+
hideOptions={this.hideOptions}
177+
onChange={this.handleChange}
178+
containerRef={this.optionsContainer}
179+
inputRef={this.input}
180+
disabled={this.props.readOnly}
181+
/>
182+
}
183+
</div>
184+
)
185+
}
186+
}
187+
188+
class FormMultiSelectInputOptions extends React.Component {
189+
componentDidMount() {
190+
document.addEventListener('mousedown', this.handleClickOutside);
191+
}
192+
193+
componentWillUnmount() {
194+
document.removeEventListener('mousedown', this.handleClickOutside);
195+
}
196+
197+
handleClickOutside = (e) => {
198+
if (this.props.containerRef.current &&
199+
!this.props.containerRef.current.contains(e.target) &&
200+
!this.props.inputRef.current.contains(e.target)
201+
)
202+
this.props.hideOptions();
203+
};
204+
205+
render() {
206+
return (
207+
<div ref={this.props.containerRef}>
208+
<div className="rjf-multiselect-field-options-container">
209+
{this.props.options.map((option, i) => {
210+
let label, inputValue;
211+
if (typeof option === 'object') {
212+
label = option.label;
213+
inputValue = option.value;
214+
} else {
215+
label = option;
216+
if (typeof label === 'boolean')
217+
label = capitalize(label.toString());
218+
inputValue = option;
219+
}
220+
221+
let selected = this.props.value.indexOf(inputValue) > -1;
222+
223+
let optionClassName = 'rjf-multiselect-field-option';
224+
if (selected)
225+
optionClassName += ' selected';
226+
if (this.props.disabled)
227+
optionClassName += ' disabled';
228+
229+
return (
230+
<div key={label + '_' + inputValue + '_' + i} className={optionClassName}>
231+
<label>
232+
<input
233+
type="checkbox"
234+
onChange={this.props.onChange}
235+
value={inputValue}
236+
checked={selected}
237+
disabled={this.props.disabled}
238+
/> {label}
239+
</label>
240+
</div>
241+
);
242+
})}
243+
</div>
244+
</div>
245+
);
246+
}
247+
248+
}
249+
114250
export function dataURItoBlob(dataURI) {
115251
// Split metadata from data
116252
const splitted = dataURI.split(",");

src/components/index.js

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

88
export {
99
Button,
1010
FormInput, FormCheckInput, FormRadioInput, FormSelectInput, FormFileInput,
11-
FormTextareaInput, FormDateTimeInput,
11+
FormTextareaInput, FormDateTimeInput, FormMultiSelectInput,
1212
FormRow, FormGroup, FormRowControls,
1313
Loader,
1414
Icon,

src/ui.js

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {getBlankData} from './data';
22
import {Button, FormInput, FormCheckInput, FormRadioInput, FormSelectInput,
33
FormFileInput, FormRow, FormGroup, FormRowControls, FormTextareaInput,
4-
FormDateTimeInput} from './components';
4+
FormDateTimeInput, FormMultiSelectInput} from './components';
55
import {getVerboseName} from './util';
66

77

@@ -89,6 +89,9 @@ function FormField(props) {
8989
case 'select':
9090
InputField = FormSelectInput;
9191
break;
92+
case 'multiselect':
93+
InputField = FormMultiSelectInput;
94+
break;
9295
case 'textarea':
9396
InputField = FormTextareaInput;
9497
break;
@@ -114,7 +117,7 @@ function FormField(props) {
114117
export function getStringFormRow(args) {
115118
let {
116119
data, schema, name, onChange, onRemove, removable, onEdit, editable,
117-
onMoveUp, onMoveDown
120+
onMoveUp, onMoveDown, parentType, ...fieldProps
118121
} = args;
119122

120123
return (
@@ -131,6 +134,8 @@ export function getStringFormRow(args) {
131134
onChange={onChange}
132135
onEdit={onEdit}
133136
editable={editable}
137+
parentType={parentType}
138+
{...fieldProps}
134139
/>
135140
</FormRow>
136141
);
@@ -167,29 +172,41 @@ export function getArrayFormRow(args) {
167172
level: level + 1,
168173
removable: removable,
169174
onMove: onMove,
175+
parentType: 'array',
170176
};
171177

172-
for (let i = 0; i < data.length; i++) {
173-
nextArgs.data = data[i];
174-
nextArgs.name = name + '-' + i;
175-
176-
if (i === 0)
177-
nextArgs.onMoveUp = null;
178-
else
179-
nextArgs.onMoveUp = (e) => onMove(name + '-' + i, name + '-' + (i - 1));
180-
181-
if (i === data.length - 1)
182-
nextArgs.onMoveDown = null;
183-
else
184-
nextArgs.onMoveDown = (e) => onMove(name + '-' + i, name + '-' + (i + 1));
178+
if (nextArgs.schema.widget === 'multiselect') {
179+
nextArgs.data = data;
180+
nextArgs.name = name;
181+
nextArgs.removable = false;
182+
nextArgs.onMoveUp = null;
183+
nextArgs.onMoveDown = null;
184+
addable = false;
185+
rows.push(getStringFormRow(nextArgs));
186+
} else {
185187

186-
if (type === 'array') {
187-
groups.push(getArrayFormRow(nextArgs));
188-
} else if (type === 'object') {
189-
groups.push(getObjectFormRow(nextArgs));
190-
} else {
191-
rows.push(getStringFormRow(nextArgs));
192-
}
188+
for (let i = 0; i < data.length; i++) {
189+
nextArgs.data = data[i];
190+
nextArgs.name = name + '-' + i;
191+
192+
if (i === 0)
193+
nextArgs.onMoveUp = null;
194+
else
195+
nextArgs.onMoveUp = (e) => onMove(name + '-' + i, name + '-' + (i - 1));
196+
197+
if (i === data.length - 1)
198+
nextArgs.onMoveDown = null;
199+
else
200+
nextArgs.onMoveDown = (e) => onMove(name + '-' + i, name + '-' + (i + 1));
201+
202+
if (type === 'array') {
203+
groups.push(getArrayFormRow(nextArgs));
204+
} else if (type === 'object') {
205+
groups.push(getObjectFormRow(nextArgs));
206+
} else {
207+
rows.push(getStringFormRow(nextArgs));
208+
}
209+
}
193210
}
194211

195212
let coords = name; // coordinates for insertion and deletion
@@ -283,7 +300,8 @@ export function getObjectFormRow(args) {
283300
onRemove: onRemove,
284301
level: level + 1,
285302
removable: removable,
286-
onMove: onMove
303+
onMove: onMove,
304+
parentType: 'object',
287305
};
288306

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

0 commit comments

Comments
 (0)