components.widgets.List.List.jsx Maven / Gradle / Ivy
The newest version!
import React, { Component } from 'react'
import classNames from 'classnames'
import ReactDom from 'react-dom'
import isEqual from 'lodash/isEqual'
import isEmpty from 'lodash/isEmpty'
import PropTypes from 'prop-types'
import {
WindowScroller,
AutoSizer,
CellMeasurer,
CellMeasurerCache,
List as Virtualizer,
} from 'react-virtualized'
import { getIndex } from '../Table/utils'
import ListItem from './ListItem'
import ListMoreButton from './ListMoreButton'
const SCROLL_OFFSET = 100
/**
* Компонент List
* @reactProps {boolean} autoFocus - фокус при инициализации на перой или на selectedId строке
* @reactProps {function} onItemClick - callback при клике на строку
* @reactProps {object} rowClick - кастомное действие клика
* @reactProps {function} onFetchMore - callback при клика на "Загрузить еще" или скролле
* @reactProps {string|number} selectedId - id выбранной записи
*/
/* FIXME виджет требует рефакторинга frontend + backend xml api
избавиться от react-virtualized */
class List extends Component {
constructor(props) {
super(props)
this.state = {
selectedIndex: props.hasSelect
? getIndex(props.data, props.selectedId)
: null,
data: props.data,
}
this.cache = new CellMeasurerCache({
fixedWidth: true,
defaultHeight: 90,
minHeight: 60,
})
this.scrollTimeoutId = null
this.renderRow = this.renderRow.bind(this)
this.onItemClick = this.onItemClick.bind(this)
this.fetchMore = this.fetchMore.bind(this)
this.onScroll = this.onScroll.bind(this)
this.setListContainerRef = this.setListContainerRef.bind(this)
this.setVirtualizerRef = this.setVirtualizerRef.bind(this)
this.setWindowScrollerRef = this.setWindowScrollerRef.bind(this)
}
componentDidMount() {
const { fetchOnScroll } = this.props
if (fetchOnScroll) {
this.listContainer.addEventListener('scroll', this.onScroll, true)
}
}
componentDidUpdate(prevProps) {
const {
data,
hasMoreButton,
fetchOnScroll,
maxHeight,
selectedId,
} = this.props
if (!isEqual(prevProps, this.props)) {
let state = {}
if (hasMoreButton && !fetchOnScroll && !isEqual(prevProps.data, data)) {
if (maxHeight) {
this.virtualizer.scrollToRow(data.length)
} else {
// eslint-disable-next-line react/no-find-dom-node
const virtualizer = ReactDom.findDOMNode(this.virtualizer)
if (virtualizer) {
window.scrollTo(0, virtualizer.scrollHeight)
}
}
}
if (!isEqual(prevProps.data, data)) {
state = {
...state,
data: hasMoreButton ? [...data, {}] : data,
}
}
if (selectedId && !isEqual(prevProps.selectedId, selectedId)) {
state = {
...state,
selectedIndex: getIndex(data, selectedId),
}
}
this.setState(state, () => {
if (this.virtualizer) {
// noinspection JSUnresolvedFunction
this.virtualizer.forceUpdateGrid()
}
this.resizeAll()
})
}
}
componentWillUnmount() {
const { fetchOnScroll } = this.props
if (fetchOnScroll) {
this.listContainer.removeEventListener('scroll', this.onScroll)
}
}
setListContainerRef(el) {
this.listContainer = el
}
setWindowScrollerRef(el) {
this.windowScroller = el
}
setVirtualizerRef(el) {
this.virtualizer = el
}
onItemClick(index, runCallback = true) {
const { onItemClick, rowClick, hasSelect } = this.props
if (!rowClick && hasSelect) {
this.setState({ selectedIndex: index }, () => {
if (this.virtualizer) {
// noinspection JSUnresolvedFunction
this.virtualizer.forceUpdateGrid()
}
})
}
if (runCallback) { onItemClick(index) }
}
fetchMore() {
const { onFetchMore } = this.props
onFetchMore()
}
onScroll(event) {
clearTimeout(this.scrollTimeoutId)
this.scrollTimeoutId = setTimeout(() => {
const scrollPosition = event.target.scrollTop + event.target.clientHeight
const minScrollToLoad = event.target.scrollHeight - SCROLL_OFFSET
if (
scrollPosition >= minScrollToLoad ||
scrollPosition === event.target.scrollHeight
) {
this.fetchMore()
}
}, 300)
}
resizeAll = () => {
this.cache.clearAll()
if (this.virtualizer) {
// noinspection JSUnresolvedFunction
this.virtualizer.recomputeRowHeights()
}
}
renderRow({ index, key, style, parent }) {
const {
divider,
hasMoreButton,
fetchOnScroll,
hasSelect,
rows,
} = this.props
const { data, selectedIndex } = this.state
const moreBtn = null
if (index === data.length - 1 && hasMoreButton && !fetchOnScroll) {
return (
)
}
return (
<>
{
({ measure }) => (
this.onItemClick(index, isEmpty(rows) || !rows.disabled)}
measure={measure}
/>
) }
{moreBtn}
>
)
}
render() {
const { className, maxHeight, t } = this.props
const { data } = this.state
return (
{(!data || isEmpty(data)) && (
{t('noData')}
)}
{data && !isEmpty(data) && (
{maxHeight ? (
{({ width }) => (
)}
) : (
{({
height,
isScrolling,
onChildScroll,
scrollTop,
}) => (
{({ width }) => (
)}
)}
)}
)}
)
}
}
List.propTypes = {
/**
* Callback на клик по строке
*/
onItemClick: PropTypes.func,
/**
* Флаг включения выбора строк
*/
hasSelect: PropTypes.bool,
/**
* Класс
*/
className: PropTypes.string,
/**
* Данные
*/
data: PropTypes.arrayOf(PropTypes.object),
/**
* Экшен клика по строке
*/
rowClick: PropTypes.object,
/**
* Флаг включения кнопки "Загрузить еще"
*/
hasMoreButton: PropTypes.bool,
/**
* Callback на "Загрузить еще"
*/
onFetchMore: PropTypes.func,
/**
* Максимальная высота
*/
maxHeight: PropTypes.number,
/**
* Флаг получения данных при скролле
*/
fetchOnScroll: PropTypes.bool,
/**
* Линия разделитель
*/
divider: PropTypes.bool,
rows: PropTypes.object,
selectedId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
t: PropTypes.func,
}
List.defaultProps = {
onItemClick: () => {},
onFetchMore: () => {},
t: () => {},
hasSelect: false,
data: [],
rowClick: false,
hasMoreButton: false,
fetchOnScroll: false,
divider: true,
rows: {},
}
export { List }
export default List