insomnia/app/components/base/KeyValueEditor.js

235 lines
6.5 KiB
JavaScript
Raw Normal View History

import React, {Component, PropTypes} from 'react';
import classnames from 'classnames';
import Input from '../base/Input';
2016-04-09 01:14:25 +00:00
const NAME = 'name';
const VALUE = 'value';
const ENTER = 13;
const BACKSPACE = 8;
const UP = 38;
const DOWN = 40;
2016-04-09 01:14:25 +00:00
class KeyValueEditor extends Component {
constructor (props) {
super(props);
2016-06-20 06:05:40 +00:00
this._focusedPair = -1;
this._focusedField = NAME;
2016-06-20 06:05:40 +00:00
this.state = {
pairs: props.pairs
}
}
2016-05-01 19:56:30 +00:00
_onChange (pairs) {
this.props.onChange(pairs);
}
_addPair (position) {
position = position === undefined ? this.state.pairs.length : position;
this._focusedPair = position;
const pairs = [
...this.state.pairs.slice(0, position),
{name: '', value: ''},
...this.state.pairs.slice(position)
];
this._onChange(pairs);
}
_deletePair (position) {
if (this._focusedPair >= position) {
this._focusedPair = this._focusedPair - 1;
}
this._onChange(this.state.pairs.filter((_, i) => i !== position));
2016-04-09 01:14:25 +00:00
}
_updatePair (position, pairPatch) {
const pairs = this.state.pairs.map(
(p, i) => i == position ? Object.assign({}, p, pairPatch) : p
2016-04-09 01:14:25 +00:00
);
this._onChange(pairs, true);
}
_focusNext (addIfValue = false) {
if (this._focusedField === NAME) {
this._focusedField = VALUE;
this._updateFocus();
} else if (this._focusedField === VALUE) {
this._focusedField = NAME;
if (addIfValue) {
this._addPair(this._focusedPair + 1);
} else {
this._focusNextPair();
}
}
}
_focusPrevious (deleteIfEmpty = false) {
if (this._focusedField === VALUE) {
this._focusedField = NAME;
this._updateFocus();
} else if (this._focusedField === NAME) {
const pair = this.state.pairs[this._focusedPair];
if (!pair.name && !pair.value && deleteIfEmpty) {
this._focusedField = VALUE;
this._deletePair(this._focusedPair);
} else if (!pair.name) {
this._focusedField = VALUE;
this._focusPreviousPair();
}
}
}
_focusNextPair () {
if (this._focusedPair >= this.state.pairs.length - 1) {
this._addPair();
} else {
this._focusedPair++;
this._updateFocus();
}
}
_focusPreviousPair () {
if (this._focusedPair >= 0) {
this._focusedPair--;
this._updateFocus();
}
}
_keyDown (e) {
if (e.keyCode === ENTER) {
e.preventDefault();
this._focusNext(true);
} else if (e.keyCode === BACKSPACE) {
if (!e.target.value) {
e.preventDefault();
this._focusPrevious(true);
}
} else if (e.keyCode === DOWN) {
e.preventDefault();
this._focusNextPair();
} else if (e.keyCode === UP) {
e.preventDefault();
this._focusPreviousPair();
}
}
_updateFocus () {
const refName = `${this._focusedPair}.${this._focusedField}`;
const ref = this.refs[refName];
2016-04-09 21:08:55 +00:00
if (ref) {
ref.focus();
// Focus at the end of the text
2016-05-08 21:14:59 +00:00
ref.selectionStart = ref.selectionEnd = ref.getValue().length;
}
}
shouldComponentUpdate (nextProps) {
2016-05-08 21:14:59 +00:00
// Compare uniqueness key (quick)
if (nextProps.uniquenessKey !== this.props.uniquenessKey) {
return true;
}
// Compare array length (quick)
if (nextProps.pairs.length !== this.state.pairs.length) {
return true;
}
// Compare arrays
for (let i = 0; i < nextProps.pairs.length; i++) {
let newPair = nextProps.pairs[i];
let oldPair = this.state.pairs[i];
if (newPair.name !== oldPair.name || newPair.value !== oldPair.value) {
return true;
}
}
return false;
}
componentWillReceiveProps (nextProps) {
this.setState({pairs: nextProps.pairs})
}
componentDidUpdate () {
this._updateFocus();
2016-04-09 01:14:25 +00:00
}
render () {
const {pairs} = this.state;
2016-04-17 22:46:17 +00:00
const {maxPairs, className} = this.props;
2016-04-09 01:14:25 +00:00
return (
2016-05-01 19:56:30 +00:00
<ul className={classnames('key-value-editor', 'wide', className)}>
{pairs.map((pair, i) => {
2016-06-19 19:20:05 +00:00
if (typeof pair.value !== 'string') {
return null;
}
return (
2016-05-01 19:56:30 +00:00
<li key={i}>
<div className="form-control form-control--underlined form-control--wide">
2016-05-08 21:14:59 +00:00
<Input
type="text"
placeholder={this.props.namePlaceholder || 'Name'}
ref={`${i}.${NAME}`}
2016-05-08 21:14:59 +00:00
value={pair.name}
onChange={name => this._updatePair(i, {name})}
2016-06-18 21:31:17 +00:00
onFocus={() => {this._focusedPair = i; this._focusedField = NAME}}
onBlur={() => {this._focusedPair = -1}}
onKeyDown={this._keyDown.bind(this)}/>
</div>
2016-05-01 19:56:30 +00:00
<div className="form-control form-control--underlined form-control--wide">
2016-05-08 21:14:59 +00:00
<Input
type="text"
placeholder={this.props.valuePlaceholder || 'Value'}
ref={`${i}.${VALUE}`}
2016-05-08 21:14:59 +00:00
value={pair.value}
onChange={value => this._updatePair(i, {value})}
2016-06-18 21:31:17 +00:00
onFocus={() => {this._focusedPair = i; this._focusedField = VALUE}}
onBlur={() => {this._focusedPair = -1}}
onKeyDown={this._keyDown.bind(this)}/>
</div>
2016-05-01 19:56:30 +00:00
<button tabIndex="-1" onClick={e => this._deletePair(i)}>
<i className="fa fa-trash-o"></i>
</button>
</li>
)
})}
2016-04-12 06:03:52 +00:00
{maxPairs === undefined || pairs.length < maxPairs ? (
2016-05-01 19:56:30 +00:00
<li>
<div className="form-control form-control--underlined form-control--wide">
2016-04-09 21:08:55 +00:00
<input type="text"
placeholder={this.props.namePlaceholder || 'Name'}
2016-06-18 21:31:17 +00:00
onFocus={() => {this._focusedField = NAME; this._addPair()}}/>
2016-04-09 21:08:55 +00:00
</div>
2016-05-01 19:56:30 +00:00
<div className="form-control form-control--underlined form-control--wide">
2016-04-09 21:08:55 +00:00
<input type="text"
placeholder={this.props.valuePlaceholder || 'Value'}
2016-06-18 21:31:17 +00:00
onFocus={() => {this._focusedField = VALUE; this._addPair()}}/>
2016-04-09 21:08:55 +00:00
</div>
2016-05-01 19:56:30 +00:00
<button disabled={true} tabIndex="-1">
<i className="fa fa-blank"></i>
</button>
</li>
2016-04-09 21:08:55 +00:00
) : null}
2016-05-01 19:56:30 +00:00
</ul>
2016-04-09 01:14:25 +00:00
)
}
}
KeyValueEditor.propTypes = {
onChange: PropTypes.func.isRequired,
uniquenessKey: PropTypes.string.isRequired,
2016-04-09 21:08:55 +00:00
pairs: PropTypes.array.isRequired,
maxPairs: PropTypes.number,
namePlaceholder: PropTypes.string,
valuePlaceholder: PropTypes.string
2016-04-09 01:14:25 +00:00
};
export default KeyValueEditor;