UNPKG

rmc-list-view

Version:
575 lines (477 loc) 22.3 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var _extends2 = require('babel-runtime/helpers/extends'); var _extends3 = _interopRequireDefault(_extends2); var _objectWithoutProperties2 = require('babel-runtime/helpers/objectWithoutProperties'); var _objectWithoutProperties3 = _interopRequireDefault(_objectWithoutProperties2); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _react = require('react'); var _react2 = _interopRequireDefault(_react); var _reactDom = require('react-dom'); var _reactDom2 = _interopRequireDefault(_reactDom); var _ListViewDataSource = require('./ListViewDataSource'); var _ListViewDataSource2 = _interopRequireDefault(_ListViewDataSource); var _ScrollView = require('./ScrollView'); var _ScrollView2 = _interopRequireDefault(_ScrollView); var _ScrollResponder = require('./ScrollResponder'); var _ScrollResponder2 = _interopRequireDefault(_ScrollResponder); var _StaticRenderer = require('./StaticRenderer'); var _StaticRenderer2 = _interopRequireDefault(_StaticRenderer); var _reactTimerMixin = require('react-timer-mixin'); var _reactTimerMixin2 = _interopRequireDefault(_reactTimerMixin); var _objectAssign = require('object-assign'); var _objectAssign2 = _interopRequireDefault(_objectAssign); var _reactMixin = require('react-mixin'); var _reactMixin2 = _interopRequireDefault(_reactMixin); var _autobindDecorator = require('autobind-decorator'); var _autobindDecorator2 = _interopRequireDefault(_autobindDecorator); var _reactSticky = require('react-sticky'); var _util = require('./util'); var _PullUpLoadMoreMixin = require('./PullUpLoadMoreMixin'); var _PullUpLoadMoreMixin2 = _interopRequireDefault(_PullUpLoadMoreMixin); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } var DEFAULT_PAGE_SIZE = 1; // https://github.com/facebook/react-native/blob/master/Libraries/CustomComponents/ListView/ListView.js var DEFAULT_INITIAL_ROWS = 10; var DEFAULT_SCROLL_RENDER_AHEAD = 1000; var DEFAULT_END_REACHED_THRESHOLD = 1000; var DEFAULT_SCROLL_CALLBACK_THROTTLE = 50; var SCROLLVIEW_REF = 'listviewscroll'; var ListView = function (_React$Component) { (0, _inherits3["default"])(ListView, _React$Component); function ListView() { var _temp, _this, _ret; (0, _classCallCheck3["default"])(this, ListView); for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { args[_key] = arguments[_key]; } return _ret = (_temp = (_this = (0, _possibleConstructorReturn3["default"])(this, _React$Component.call.apply(_React$Component, [this].concat(args))), _this), _this.state = { curRenderedRowsCount: _this.props.initialListSize, highlightedRow: {} }, _this.stickyRefs = {}, _temp), (0, _possibleConstructorReturn3["default"])(_this, _ret); } /** * React life cycle hooks. */ /** * Exports some data, e.g. for perf investigations or analytics. */ ListView.prototype.getMetrics = function getMetrics() { return { contentLength: this.scrollProperties.contentLength, totalRows: this.props.dataSource.getRowCount(), renderedRows: this.state.curRenderedRowsCount, visibleRows: Object.keys(this._visibleRows).length }; }; /** * Provides a handle to the underlying scroll responder. * Note that the view in `SCROLLVIEW_REF` may not be a `ScrollView`, so we * need to check that it responds to `getScrollResponder` before calling it. */ ListView.prototype.getScrollResponder = function getScrollResponder() { return this.refs[SCROLLVIEW_REF] && this.refs[SCROLLVIEW_REF].getScrollResponder && this.refs[SCROLLVIEW_REF].getScrollResponder(); }; ListView.prototype.scrollTo = function scrollTo() { var _refs$SCROLLVIEW_REF; this.refs[SCROLLVIEW_REF] && this.refs[SCROLLVIEW_REF].scrollTo && (_refs$SCROLLVIEW_REF = this.refs[SCROLLVIEW_REF]).scrollTo.apply(_refs$SCROLLVIEW_REF, arguments); }; ListView.prototype.setNativeProps = function setNativeProps(props) { this.refs[SCROLLVIEW_REF] && this.refs[SCROLLVIEW_REF].setNativeProps(props); }; ListView.prototype.getInnerViewNode = function getInnerViewNode() { return this.refs[SCROLLVIEW_REF].getInnerViewNode(); }; ListView.prototype.componentWillMount = function componentWillMount() { // this data should never trigger a render pass, so don't put in state this.scrollProperties = { visibleLength: null, contentLength: null, offset: 0 }; this._childFrames = []; this._visibleRows = {}; this._prevRenderedRowsCount = 0; this._sentEndForContentLength = null; }; ListView.prototype.componentDidMount = function componentDidMount() { // do this in animation frame until componentDidMount actually runs after // the component is laid out // this.requestAnimationFrame(() => { // this._measureAndUpdateScrollProps(); // }); if (this.props.stickyHeader || this.props.useBodyScroll) { this.__onScroll = (0, _util.throttle)(this._onScroll, this.props.scrollEventThrottle); window.addEventListener('scroll', this.__onScroll); } }; ListView.prototype.componentWillReceiveProps = function componentWillReceiveProps(nextProps) { var _this2 = this; if (this.props.dataSource !== nextProps.dataSource || this.props.initialListSize !== nextProps.initialListSize) { this.setState(function (state, props) { _this2._prevRenderedRowsCount = 0; return { curRenderedRowsCount: Math.min(Math.max(state.curRenderedRowsCount, props.initialListSize), props.dataSource.getRowCount()) }; }, function () { return _this2._renderMoreRowsIfNeeded(); }); } }; ListView.prototype.componentWillUnmount = function componentWillUnmount() { if (this.props.stickyHeader || this.props.useBodyScroll) { window.removeEventListener('scroll', this.__onScroll); } }; ListView.prototype.onRowHighlighted = function onRowHighlighted(sectionID, rowID) { this.setState({ highlightedRow: { sectionID: sectionID, rowID: rowID } }); }; ListView.prototype.render = function render() { var _this3 = this; var bodyComponents = []; var dataSource = this.props.dataSource; var allRowIDs = dataSource.rowIdentities; var rowCount = 0; var sectionHeaderIndices = []; var header = this.props.renderHeader && this.props.renderHeader(); var footer = this.props.renderFooter && this.props.renderFooter(); var totalIndex = header ? 1 : 0; var _loop = function _loop(sectionIdx) { var sectionID = dataSource.sectionIdentities[sectionIdx]; var rowIDs = allRowIDs[sectionIdx]; if (rowIDs.length === 0) { return 'continue'; } if (_this3.props.renderSectionHeader) { var shouldUpdateHeader = rowCount >= _this3._prevRenderedRowsCount && dataSource.sectionHeaderShouldUpdate(sectionIdx); var renderSectionHeader = _react2["default"].createElement(_StaticRenderer2["default"], { key: 's_' + sectionID, shouldUpdate: !!shouldUpdateHeader, render: _this3.props.renderSectionHeader.bind(null, dataSource.getSectionHeaderData(sectionIdx), sectionID) }); if (_this3.props.stickyHeader) { renderSectionHeader = _react2["default"].createElement( _reactSticky.Sticky, (0, _extends3["default"])({}, _this3.props.stickyProps, { key: 's_' + sectionID, ref: function ref(c) { _this3.stickyRefs[sectionID] = c; } }), renderSectionHeader ); } bodyComponents.push(renderSectionHeader); sectionHeaderIndices.push(totalIndex++); } var sectionBody = []; for (var rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { var rowID = rowIDs[rowIdx]; var comboID = sectionID + '_' + rowID; var shouldUpdateRow = rowCount >= _this3._prevRenderedRowsCount && dataSource.rowShouldUpdate(sectionIdx, rowIdx); var row = _react2["default"].createElement(_StaticRenderer2["default"], { key: 'r_' + comboID, shouldUpdate: !!shouldUpdateRow, render: _this3.props.renderRow.bind(null, dataSource.getRowData(sectionIdx, rowIdx), sectionID, rowID, _this3.onRowHighlighted) }); // bodyComponents.push(row); sectionBody.push(row); totalIndex++; if (_this3.props.renderSeparator && (rowIdx !== rowIDs.length - 1 || sectionIdx === allRowIDs.length - 1)) { var adjacentRowHighlighted = _this3.state.highlightedRow.sectionID === sectionID && (_this3.state.highlightedRow.rowID === rowID || _this3.state.highlightedRow.rowID === rowIDs[rowIdx + 1]); var separator = _this3.props.renderSeparator(sectionID, rowID, adjacentRowHighlighted); if (separator) { // bodyComponents.push(separator); sectionBody.push(separator); totalIndex++; } } if (++rowCount === _this3.state.curRenderedRowsCount) { break; } } bodyComponents.push(_react2["default"].cloneElement(_this3.props.renderSectionBodyWrapper(sectionID), { className: _this3.props.sectionBodyClassName }, sectionBody)); if (rowCount >= _this3.state.curRenderedRowsCount) { return 'break'; } }; _loop2: for (var sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { var _ret2 = _loop(sectionIdx); switch (_ret2) { case 'continue': continue; case 'break': break _loop2;} } var _props = this.props, renderScrollComponent = _props.renderScrollComponent, props = (0, _objectWithoutProperties3["default"])(_props, ['renderScrollComponent']); bodyComponents = _react2["default"].cloneElement(props.renderBodyComponent(), {}, bodyComponents); if (props.stickyHeader) { bodyComponents = _react2["default"].createElement( _reactSticky.StickyContainer, props.stickyContainerProps, bodyComponents ); } (0, _objectAssign2["default"])(props, { onScroll: this._onScroll }); if (props.stickyHeader || props.useBodyScroll) { delete props.onScroll; } this._sc = _react2["default"].cloneElement(renderScrollComponent(props), { ref: SCROLLVIEW_REF, onContentSizeChange: this._onContentSizeChange, onLayout: props.stickyHeader || props.useBodyScroll ? function (event) { _this3.props.onLayout && _this3.props.onLayout(event); } : this._onLayout }, header, bodyComponents, footer, props.children); return this._sc; }; /** * Private methods */ ListView.prototype._measureAndUpdateScrollProps = function _measureAndUpdateScrollProps() { var scrollComponent = this.getScrollResponder(); if (!scrollComponent || !scrollComponent.getInnerViewNode) { return; } // RCTScrollViewManager.calculateChildFrames is not available on // every platform // RCTScrollViewManager && RCTScrollViewManager.calculateChildFrames && // RCTScrollViewManager.calculateChildFrames( // React.findNodeHandle(scrollComponent), // this._updateVisibleRows, // ); }; ListView.prototype._onContentSizeChange = function _onContentSizeChange(width, height) { var contentLength = !this.props.horizontal ? height : width; if (contentLength !== this.scrollProperties.contentLength) { this.scrollProperties.contentLength = contentLength; this._updateVisibleRows(); this._renderMoreRowsIfNeeded(); } this.props.onContentSizeChange && this.props.onContentSizeChange(width, height); }; ListView.prototype._onLayout = function _onLayout(event) { var _event$nativeEvent$la = event.nativeEvent.layout, width = _event$nativeEvent$la.width, height = _event$nativeEvent$la.height; var visibleLength = !this.props.horizontal ? height : width; if (visibleLength !== this.scrollProperties.visibleLength) { this.scrollProperties.visibleLength = visibleLength; this._updateVisibleRows(); this._renderMoreRowsIfNeeded(); } this.props.onLayout && this.props.onLayout(event); }; ListView.prototype._maybeCallOnEndReached = function _maybeCallOnEndReached(event) { // console.log(this.scrollProperties, this._getDistanceFromEnd(this.scrollProperties)); if (this.props.onEndReached && // this.scrollProperties.contentLength !== this._sentEndForContentLength && this._getDistanceFromEnd(this.scrollProperties) < this.props.onEndReachedThreshold && this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) { this._sentEndForContentLength = this.scrollProperties.contentLength; this.props.onEndReached(event); return true; } return false; }; ListView.prototype._renderMoreRowsIfNeeded = function _renderMoreRowsIfNeeded() { if (this.scrollProperties.contentLength === null || this.scrollProperties.visibleLength === null || this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()) { this._maybeCallOnEndReached(); return; } var distanceFromEnd = this._getDistanceFromEnd(this.scrollProperties); // console.log(distanceFromEnd, this.props.scrollRenderAheadDistance); if (distanceFromEnd < this.props.scrollRenderAheadDistance) { this._pageInNewRows(); } }; ListView.prototype._pageInNewRows = function _pageInNewRows() { var _this4 = this; this.setState(function (state, props) { var rowsToRender = Math.min(state.curRenderedRowsCount + props.pageSize, props.dataSource.getRowCount()); _this4._prevRenderedRowsCount = state.curRenderedRowsCount; return { curRenderedRowsCount: rowsToRender }; }, function () { _this4._measureAndUpdateScrollProps(); _this4._prevRenderedRowsCount = _this4.state.curRenderedRowsCount; }); }; ListView.prototype._getDistanceFromEnd = function _getDistanceFromEnd(scrollProperties) { return scrollProperties.contentLength - scrollProperties.visibleLength - scrollProperties.offset; }; ListView.prototype._updateVisibleRows = function _updateVisibleRows(updatedFrames) { // if (!this.props.onChangeVisibleRows) { // return; // No need to compute visible rows if there is no callback // } // if (updatedFrames) { // updatedFrames.forEach((newFrame) => { // this._childFrames[newFrame.index] = merge(newFrame); // }); // } // let isVertical = !this.props.horizontal; // let dataSource = this.props.dataSource; // let visibleMin = this.scrollProperties.offset; // let visibleMax = visibleMin + this.scrollProperties.visibleLength; // let allRowIDs = dataSource.rowIdentities; // // let header = this.props.renderHeader && this.props.renderHeader(); // let totalIndex = header ? 1 : 0; // let visibilityChanged = false; // let changedRows = {}; // for (let sectionIdx = 0; sectionIdx < allRowIDs.length; sectionIdx++) { // let rowIDs = allRowIDs[sectionIdx]; // if (rowIDs.length === 0) { // continue; // } // let sectionID = dataSource.sectionIdentities[sectionIdx]; // if (this.props.renderSectionHeader) { // totalIndex++; // } // let visibleSection = this._visibleRows[sectionID]; // if (!visibleSection) { // visibleSection = {}; // } // for (let rowIdx = 0; rowIdx < rowIDs.length; rowIdx++) { // let rowID = rowIDs[rowIdx]; // let frame = this._childFrames[totalIndex]; // totalIndex++; // if (!frame) { // break; // } // let rowVisible = visibleSection[rowID]; // let min = isVertical ? frame.y : frame.x; // let max = min + (isVertical ? frame.height : frame.width); // if (min > visibleMax || max < visibleMin) { // if (rowVisible) { // visibilityChanged = true; // delete visibleSection[rowID]; // if (!changedRows[sectionID]) { // changedRows[sectionID] = {}; // } // changedRows[sectionID][rowID] = false; // } // } else if (!rowVisible) { // visibilityChanged = true; // visibleSection[rowID] = true; // if (!changedRows[sectionID]) { // changedRows[sectionID] = {}; // } // changedRows[sectionID][rowID] = true; // } // } // if (!isEmpty(visibleSection)) { // this._visibleRows[sectionID] = visibleSection; // } else if (this._visibleRows[sectionID]) { // delete this._visibleRows[sectionID]; // } // } // visibilityChanged && this.props.onChangeVisibleRows(this._visibleRows, changedRows); }; ListView.prototype._onScroll = function _onScroll(e) { var isVertical = !this.props.horizontal; // this.scrollProperties.visibleLength = e.nativeEvent.layoutMeasurement[ // isVertical ? 'height' : 'width' // ]; // this.scrollProperties.contentLength = e.nativeEvent.contentSize[ // isVertical ? 'height' : 'width' // ]; // this.scrollProperties.offset = e.nativeEvent.contentOffset[ // isVertical ? 'y' : 'x' // ]; var ev = e; var target = _reactDom2["default"].findDOMNode(this.refs[SCROLLVIEW_REF]); if (this.props.stickyHeader || this.props.useBodyScroll) { this.scrollProperties.visibleLength = window[isVertical ? 'innerHeight' : 'innerWidth']; this.scrollProperties.contentLength = target[isVertical ? 'scrollHeight' : 'scrollWidth']; this.scrollProperties.offset = window.document.body[isVertical ? 'scrollTop' : 'scrollLeft']; } else if (this.props.useZscroller) { var domScroller = this.refs[SCROLLVIEW_REF].domScroller; ev = domScroller; this.scrollProperties.visibleLength = domScroller.container[isVertical ? 'clientHeight' : 'clientWidth']; this.scrollProperties.contentLength = domScroller.content[isVertical ? 'offsetHeight' : 'offsetWidth']; this.scrollProperties.offset = domScroller.scroller.getValues()[isVertical ? 'top' : 'left']; // console.log(this.scrollProperties, domScroller.scroller.getScrollMax()) } else { this.scrollProperties.visibleLength = target[isVertical ? 'offsetHeight' : 'offsetWidth']; this.scrollProperties.contentLength = target[isVertical ? 'scrollHeight' : 'scrollWidth']; this.scrollProperties.offset = target[isVertical ? 'scrollTop' : 'scrollLeft']; } // this._updateVisibleRows(e.nativeEvent.updatedChildFrames); if (!this._maybeCallOnEndReached(ev)) { this._renderMoreRowsIfNeeded(); } if (this.props.onEndReached && this._getDistanceFromEnd(this.scrollProperties) > this.props.onEndReachedThreshold) { // Scrolled out of the end zone, so it should be able to trigger again. this._sentEndForContentLength = null; } this.props.onScroll && this.props.onScroll(ev); }; return ListView; }(_react2["default"].Component); ListView.DataSource = _ListViewDataSource2["default"]; ListView.propTypes = (0, _extends3["default"])({}, _ScrollView2["default"].propTypes, { dataSource: _react.PropTypes.instanceOf(_ListViewDataSource2["default"]).isRequired, renderSeparator: _react.PropTypes.func, renderRow: _react.PropTypes.func.isRequired, initialListSize: _react.PropTypes.number, onEndReached: _react.PropTypes.func, onEndReachedThreshold: _react.PropTypes.number, pageSize: _react.PropTypes.number, renderFooter: _react.PropTypes.func, renderHeader: _react.PropTypes.func, renderSectionHeader: _react.PropTypes.func, renderScrollComponent: _react2["default"].PropTypes.func.isRequired, scrollRenderAheadDistance: _react2["default"].PropTypes.number, onChangeVisibleRows: _react2["default"].PropTypes.func, scrollEventThrottle: _react2["default"].PropTypes.number, // removeClippedSubviews: React.PropTypes.bool, // stickyHeaderIndices: PropTypes.arrayOf(PropTypes.number), // another added renderBodyComponent: _react.PropTypes.func, renderSectionBodyWrapper: _react.PropTypes.func, sectionBodyClassName: _react.PropTypes.string, useZscroller: _react.PropTypes.bool, // for web useBodyScroll: _react.PropTypes.bool, // for web stickyHeader: _react.PropTypes.bool, // for web stickyProps: _react.PropTypes.object, // https://github.com/captivationsoftware/react-sticky/blob/master/README.md#sticky--props stickyContainerProps: _react.PropTypes.object }); ListView.defaultProps = { initialListSize: DEFAULT_INITIAL_ROWS, pageSize: DEFAULT_PAGE_SIZE, renderScrollComponent: function renderScrollComponent(props) { return _react2["default"].createElement(_ScrollView2["default"], props); }, renderBodyComponent: function renderBodyComponent() { return _react2["default"].createElement('div', null); }, renderSectionBodyWrapper: function renderSectionBodyWrapper(sectionID) { return _react2["default"].createElement('div', { key: sectionID }); }, sectionBodyClassName: 'list-view-section-body', scrollRenderAheadDistance: DEFAULT_SCROLL_RENDER_AHEAD, onEndReachedThreshold: DEFAULT_END_REACHED_THRESHOLD, scrollEventThrottle: DEFAULT_SCROLL_CALLBACK_THROTTLE, // stickyHeaderIndices: [], stickyProps: {}, stickyContainerProps: {} }; (0, _reactMixin2["default"])(ListView.prototype, _ScrollResponder2["default"].Mixin); (0, _reactMixin2["default"])(ListView.prototype, _reactTimerMixin2["default"]); (0, _reactMixin2["default"])(ListView.prototype, _PullUpLoadMoreMixin2["default"]); (0, _autobindDecorator2["default"])(ListView); ListView.isReactNativeComponent = true; exports["default"] = ListView; module.exports = exports['default'];