react-virtualized
Version:
React components for efficiently rendering large, scrollable lists and tabular data
209 lines (170 loc) • 7.74 kB
JavaScript
Object.defineProperty(exports, "__esModule", {
value: true
});
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
exports.isRangeVisible = isRangeVisible;
exports.scanForUnloadedRanges = scanForUnloadedRanges;
var _react = require('react');
var _reactAddonsShallowCompare = require('react-addons-shallow-compare');
var _reactAddonsShallowCompare2 = _interopRequireDefault(_reactAddonsShallowCompare);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
/**
* Higher-order component that manages lazy-loading for "infinite" data.
* This component decorates a virtual component and just-in-time prefetches rows as a user scrolls.
* It is intended as a convenience component; fork it if you'd like finer-grained control over data-loading.
*/
var InfiniteLoader = function (_Component) {
_inherits(InfiniteLoader, _Component);
function InfiniteLoader(props, context) {
_classCallCheck(this, InfiniteLoader);
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(InfiniteLoader).call(this, props, context));
_this._onRowsRendered = _this._onRowsRendered.bind(_this);
_this._registerChild = _this._registerChild.bind(_this);
return _this;
}
_createClass(InfiniteLoader, [{
key: 'render',
value: function render() {
var children = this.props.children;
return children({
onRowsRendered: this._onRowsRendered,
registerChild: this._registerChild
});
}
}, {
key: 'shouldComponentUpdate',
value: function shouldComponentUpdate(nextProps, nextState) {
return (0, _reactAddonsShallowCompare2.default)(this, nextProps, nextState);
}
}, {
key: '_onRowsRendered',
value: function _onRowsRendered(_ref) {
var _this2 = this;
var startIndex = _ref.startIndex;
var stopIndex = _ref.stopIndex;
var _props = this.props;
var isRowLoaded = _props.isRowLoaded;
var loadMoreRows = _props.loadMoreRows;
var rowsCount = _props.rowsCount;
var threshold = _props.threshold;
this._lastRenderedStartIndex = startIndex;
this._lastRenderedStopIndex = stopIndex;
var unloadedRanges = scanForUnloadedRanges({
isRowLoaded: isRowLoaded,
startIndex: Math.max(0, startIndex - threshold),
stopIndex: Math.min(rowsCount, stopIndex + threshold)
});
unloadedRanges.forEach(function (unloadedRange) {
var promise = loadMoreRows(unloadedRange);
if (promise) {
promise.then(function () {
// Refresh the visible rows if any of them have just been loaded.
// Otherwise they will remain in their unloaded visual state.
if (isRangeVisible({
lastRenderedStartIndex: _this2._lastRenderedStartIndex,
lastRenderedStopIndex: _this2._lastRenderedStopIndex,
startIndex: unloadedRange.startIndex,
stopIndex: unloadedRange.stopIndex
})) {
if (_this2._registeredChild) {
_this2._registeredChild.forceUpdate();
}
}
});
}
});
}
}, {
key: '_registerChild',
value: function _registerChild(registeredChild) {
this._registeredChild = registeredChild;
}
}]);
return InfiniteLoader;
}(_react.Component);
/**
* Determines if the specified start/stop range is visible based on the most recently rendered range.
*/
InfiniteLoader.propTypes = {
/**
* Function respondible for rendering a virtualized component.
* This function should implement the following signature:
* ({ onRowsRendered, registerChild }) => PropTypes.element
*
* The specified :onRowsRendered function should be passed through to the child's :onRowsRendered property.
* The :registerChild callback should be set as the virtualized component's :ref.
*/
children: _react.PropTypes.func.isRequired,
/**
* Function responsible for tracking the loaded state of each row.
* It should implement the following signature: (index: number): boolean
*/
isRowLoaded: _react.PropTypes.func.isRequired,
/**
* Callback to be invoked when more rows must be loaded.
* It should implement the following signature: ({ startIndex, stopIndex }): Promise
* The returned Promise should be resolved once row data has finished loading.
* It will be used to determine when to refresh the list with the newly-loaded data.
* This callback may be called multiple times in reaction to a single scroll event.
*/
loadMoreRows: _react.PropTypes.func.isRequired,
/**
* Number of rows in list; can be arbitrary high number if actual number is unknown.
*/
rowsCount: _react.PropTypes.number.isRequired,
/**
* Threshold at which to pre-fetch data.
* A threshold X means that data will start loading when a user scrolls within X rows.
* This value defaults to 15.
*/
threshold: _react.PropTypes.number.isRequired
};
InfiniteLoader.defaultProps = {
rowsCount: 0,
threshold: 15
};
exports.default = InfiniteLoader;
function isRangeVisible(_ref2) {
var lastRenderedStartIndex = _ref2.lastRenderedStartIndex;
var lastRenderedStopIndex = _ref2.lastRenderedStopIndex;
var startIndex = _ref2.startIndex;
var stopIndex = _ref2.stopIndex;
return !(startIndex > lastRenderedStopIndex || stopIndex < lastRenderedStartIndex);
}
/**
* Returns all of the ranges within a larger range that contain unloaded rows.
*/
function scanForUnloadedRanges(_ref3) {
var isRowLoaded = _ref3.isRowLoaded;
var startIndex = _ref3.startIndex;
var stopIndex = _ref3.stopIndex;
var unloadedRanges = [];
var rangeStartIndex = null;
var rangeStopIndex = null;
for (var i = startIndex; i <= stopIndex; i++) {
var loaded = isRowLoaded(i);
if (!loaded) {
rangeStopIndex = i;
if (rangeStartIndex === null) {
rangeStartIndex = i;
}
} else if (rangeStopIndex !== null) {
unloadedRanges.push({
startIndex: rangeStartIndex,
stopIndex: rangeStopIndex
});
rangeStartIndex = rangeStopIndex = null;
}
}
if (rangeStopIndex !== null) {
unloadedRanges.push({
startIndex: rangeStartIndex,
stopIndex: rangeStopIndex
});
}
return unloadedRanges;
}
;