Virtual-scroll backed autocomplete for react-toolbox
commit
ce8c6985d6
|
@ -0,0 +1,198 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { VirtualScrollList } from 'dynamic-virtual-scroll/VirtualScrollList.js';
|
||||||
|
import { Spinner24 } from './LoadingOverlay.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Наследование компонентов react-toolbox'а делается, но через такую жопууууу....
|
||||||
|
*/
|
||||||
|
import { Input } from 'react-toolbox/lib/input';
|
||||||
|
import { Chip } from 'react-toolbox/lib/chip';
|
||||||
|
import { autocompleteFactory } from 'react-toolbox/lib/autocomplete/Autocomplete.js';
|
||||||
|
import autocomplete_theme from 'react-toolbox/lib/autocomplete/theme.css';
|
||||||
|
import { themr } from 'react-css-themr';
|
||||||
|
import { AUTOCOMPLETE } from 'react-toolbox/lib/identifiers.js';
|
||||||
|
|
||||||
|
const RawAutocomplete = autocompleteFactory(Chip, Input);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Автокомплит для отображения БОЛЬШОГО ДЕРЕВА
|
||||||
|
*/
|
||||||
|
class RawVirtualTreeAutocomplete extends RawAutocomplete
|
||||||
|
{
|
||||||
|
static propTypes = {
|
||||||
|
...RawAutocomplete.constructor.propTypes,
|
||||||
|
maxHeight: PropTypes.number,
|
||||||
|
parentIdField: PropTypes.string.isRequired,
|
||||||
|
leafOnly: PropTypes.bool,
|
||||||
|
renderItem: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
tree = []
|
||||||
|
|
||||||
|
renderSuggestion = (idx) =>
|
||||||
|
{
|
||||||
|
const { theme } = this.props;
|
||||||
|
const item = this.tree[idx];
|
||||||
|
if (!item)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const key = item.item[this.props.valueKey];
|
||||||
|
const enabled = !this.props.leafOnly || !this.by_parent[key];
|
||||||
|
const style = { paddingLeft: (10+item.level*16)+'px', color: enabled ? '' : 'gray' };
|
||||||
|
if (this.valueHash[key])
|
||||||
|
{
|
||||||
|
style.background = '#e5e8ea';
|
||||||
|
}
|
||||||
|
let text;
|
||||||
|
if (this.props.renderItem)
|
||||||
|
{
|
||||||
|
text = this.props.renderItem(item.item, item.level, style);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
text = item.item[this.props.labelKey];
|
||||||
|
}
|
||||||
|
return (<div
|
||||||
|
id={key}
|
||||||
|
onMouseDown={enabled ? this.handleMouseDown : undefined}
|
||||||
|
onMouseOver={enabled ? this.handleSuggestionHover : undefined}
|
||||||
|
className={theme.suggestion+(this.state.active == key ? ' '+theme.active : '')}
|
||||||
|
key={key}
|
||||||
|
style={style}>
|
||||||
|
{text}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
setListScroll = (e) =>
|
||||||
|
{
|
||||||
|
if (e)
|
||||||
|
{
|
||||||
|
let k = null;
|
||||||
|
for (let i in this.valueHash)
|
||||||
|
{
|
||||||
|
k = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (k)
|
||||||
|
{
|
||||||
|
// Если есть значение, при изначальном появлении списка проскроллим к нему
|
||||||
|
let pos = this.tree.findIndex(e => e.item[this.props.valueKey] == k);
|
||||||
|
e.scrollToItem(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSuggestionList()
|
||||||
|
{
|
||||||
|
const { theme } = this.props;
|
||||||
|
const { top, bottom, maxHeight, left, width } = this.state;
|
||||||
|
let maxh = Number((''+maxHeight).replace('px', ''));
|
||||||
|
if (this.props.maxHeight && maxh > this.props.maxHeight)
|
||||||
|
{
|
||||||
|
maxh = this.props.maxHeight;
|
||||||
|
}
|
||||||
|
return (<div style={{position: 'absolute', top, left, width, maxHeight}}>
|
||||||
|
<VirtualScrollList
|
||||||
|
className={theme.suggestions}
|
||||||
|
style={{maxHeight: maxh+'px', bottom, position: 'absolute'}}
|
||||||
|
header={((!this.props.source||[]).length ? <div><Spinner24 /> Идёт загрузка...</div> : null)}
|
||||||
|
totalItems={(this.tree||[]).length}
|
||||||
|
minRowHeight={36}
|
||||||
|
viewportHeight={maxh}
|
||||||
|
renderItem={this.renderSuggestion}
|
||||||
|
ref={this.setListScroll}
|
||||||
|
/>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
addItems(parent, level, add_all)
|
||||||
|
{
|
||||||
|
for (let item of this.by_parent[parent]||[])
|
||||||
|
{
|
||||||
|
if (add_all || !this.filtered || this.filtered[item[this.props.valueKey]])
|
||||||
|
{
|
||||||
|
this.tree.push({ item, level });
|
||||||
|
this.addItems(item[this.props.valueKey], level+1, add_all || this.filtered && this.filtered[item[this.props.valueKey]] == 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render()
|
||||||
|
{
|
||||||
|
if (this.state.focus)
|
||||||
|
{
|
||||||
|
if (this.props.source != this.prevSource)
|
||||||
|
{
|
||||||
|
this.state.expanded = {};
|
||||||
|
const pf = this.props.parentIdField;
|
||||||
|
const idf = this.props.valueKey;
|
||||||
|
let by_parent = {};
|
||||||
|
let by_id = {};
|
||||||
|
for (let item of this.props.source)
|
||||||
|
{
|
||||||
|
by_id[item[idf]] = item;
|
||||||
|
by_parent[item[pf]||''] = by_parent[item[pf]||''] || [];
|
||||||
|
by_parent[item[pf]||''].push(item);
|
||||||
|
}
|
||||||
|
this.by_id = by_id;
|
||||||
|
this.by_parent = by_parent;
|
||||||
|
}
|
||||||
|
if (this.state.expanded != this.prevExpanded ||
|
||||||
|
this.state.query != this.prevQuery)
|
||||||
|
{
|
||||||
|
if (this.state.query != this.prevQuery ||
|
||||||
|
this.props.source != this.prevSource)
|
||||||
|
{
|
||||||
|
this.filtered = null;
|
||||||
|
if (this.state.query)
|
||||||
|
{
|
||||||
|
const pf = this.props.parentIdField;
|
||||||
|
this.filtered = {};
|
||||||
|
for (let k of this.suggestions().keys())
|
||||||
|
{
|
||||||
|
this.filtered[k] = 2;
|
||||||
|
let c = k;
|
||||||
|
while (c)
|
||||||
|
{
|
||||||
|
this.filtered[c] = this.filtered[c] || 1;
|
||||||
|
if (!this.by_id[c])
|
||||||
|
break;
|
||||||
|
c = this.by_id[c][pf];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// FIXME Здесь могло быть ваше раскрытие узлов дерева
|
||||||
|
this.tree = [];
|
||||||
|
this.addItems('', 0, false);
|
||||||
|
}
|
||||||
|
if (!this.valueHash || this.props.value != this.prevValue)
|
||||||
|
{
|
||||||
|
if (!this.props.value)
|
||||||
|
{
|
||||||
|
this.valueHash = {};
|
||||||
|
}
|
||||||
|
else if (this.props.multiple)
|
||||||
|
{
|
||||||
|
this.valueHash = (this.props.value instanceof Array
|
||||||
|
? this.props.value.reduce((a, c) => { a[c] = true; return a; }, {})
|
||||||
|
: this.props.value);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.valueHash = { [this.props.value]: true };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.prevExpanded = this.state.expanded;
|
||||||
|
this.prevQuery = this.state.query;
|
||||||
|
this.prevSource = this.props.source;
|
||||||
|
this.prevValue = this.props.value;
|
||||||
|
}
|
||||||
|
return super.render();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const VirtualTreeAutocomplete = themr(AUTOCOMPLETE, autocomplete_theme, { withRef: true })(RawVirtualTreeAutocomplete);
|
Loading…
Reference in New Issue