Skip to content

Instantly share code, notes, and snippets.

@lusentis
Last active October 3, 2017 05:11
Show Gist options
  • Save lusentis/e7e6191286135a9ea671 to your computer and use it in GitHub Desktop.
Save lusentis/e7e6191286135a9ea671 to your computer and use it in GitHub Desktop.
multiple select with filters, like Django's
import React, { PropTypes as Type } from 'react';
import R from 'ramda';
const textFilter = str =>
R.filter(
R.compose(
R.test(new RegExp(str, 'i')),
R.prop('text'),
)
);
export const textValueShape = Type.shape({
text: Type.string.isRequired,
value: Type.string.isRequired,
});
function handleDragStartFactory(sourceIndex) {
return e => {
e.dataTransfer.effectAllowed = 'move'; // eslint-disable-line no-param-reassign
e.dataTransfer.dropEffect = 'move'; // eslint-disable-line no-param-reassign
e.dataTransfer.setData('text/plain', `${sourceIndex}`);
};
}
function handleDragOverFactory() {
return e => {
// we need to preventDefault otherwise effectAllowed will be cleared
// and no onDrop event will be fired
// https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/effectAllowed
e.preventDefault();
};
}
function handleDropFactory(targetIndex) {
return e => {
e.preventDefault();
const { chosen } = this.state;
const sourceIndex = Number(e.dataTransfer.getData('text/plain'));
const source = chosen[sourceIndex];
const withoutSource = R.remove(sourceIndex, 1, chosen);
const withTarget = R.insert(targetIndex, source, withoutSource);
this.setState({
chosen: withTarget,
});
this.props.onChange(withTarget);
};
}
function handleChooseClickFactory(item) {
return e => {
e.preventDefault();
const { chosen } = this.state;
const nextChosen = [...chosen, item];
this.setState({
chosen: nextChosen,
});
this.props.onChange(nextChosen);
};
}
function handleRemoveClickFactory(item) {
return e => {
e.preventDefault();
const { chosen } = this.state;
const nextChosen = R.without([item], chosen); // 1st param to R.without *must* be an array
this.setState({
chosen: nextChosen,
});
this.props.onChange(nextChosen);
};
}
function handleFilterChange(e) {
e.preventDefault();
const nextFilter = e.target.value;
this.setState({
filter: nextFilter,
});
}
function handleClearClick(e) {
e.preventDefault();
this.setState({
filter: null,
});
}
export default class MultipleSelect extends React.Component {
static propTypes = {
choices: Type.arrayOf(textValueShape).isRequired,
value: Type.arrayOf(textValueShape).isRequired,
onChange: Type.func.isRequired,
}
constructor(props) {
super(props);
this.state = {
chosen: props.value,
};
}
render() {
const { choices } = this.props;
const { filter, chosen } = this.state;
const choicesWithoutChosen = R.without(chosen, choices);
const filteredChoices =
filter ?
textFilter(filter)(choicesWithoutChosen) :
choicesWithoutChosen;
return (
<div>
<p>Available</p>
<p>
Filtra: <input type="text" value={filter} onChange={this::handleFilterChange} />
<a href="#" onClick={this::handleClearClick}>pulisci</a>
</p>
<ul>
{filteredChoices.map(item =>
<li key={item.value}>
{item.text} ({item.value})
<button type="submit" onClick={this::handleChooseClickFactory(item)}>Choose</button>
</li>
)}
</ul>
<p>Chosen</p>
<ul>
{chosen.map((item, index) =>
<li
draggable
key={item.value}
onDragOver={this::handleDragOverFactory(index)}
onDragStart={this::handleDragStartFactory(index)}
onDrop={this::handleDropFactory(index)}
>
{item.text} ({item.value})
<button type="submit" onClick={this::handleRemoveClickFactory(item)}>Remove</button>
</li>
)}
</ul>
</div>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment