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