xl-infinite-tree
Version:
A browser-ready tree library that can efficiently display a large amount of data using infinite scrolling.
392 lines (305 loc) • 13.9 kB
JavaScript
'use strict';
exports.__esModule = true;
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _events = require('events');
var _ensureArray = require('./ensure-array');
var _ensureArray2 = _interopRequireDefault(_ensureArray);
var _browser = require('./browser');
var _dom = require('./dom');
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; }
var ie = (0, _browser.getIEVersion)();
var Clusterize = function (_EventEmitter) {
_inherits(Clusterize, _EventEmitter);
function Clusterize(options) {
_classCallCheck(this, Clusterize);
var _this = _possibleConstructorReturn(this, _EventEmitter.call(this));
_this.options = {
rowsInBlock: 50,
blocksInCluster: 4,
tag: null,
emptyClass: '',
emptyText: '',
keepParity: true
};
_this.state = {
lastClusterIndex: -1,
itemHeight: 0,
blockHeight: 0,
clusterHeight: 0
};
_this.scrollElement = null;
_this.contentElement = null;
_this.rows = [];
_this.cache = {};
_this.scrollEventListener = function () {
var debounce = null;
return function () {
var isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0;
if (isMac) {
if (_this.contentElement.style.pointerEvents !== 'none') {
_this.contentElement.style.pointerEvents = 'none';
}
if (debounce) {
clearTimeout(debounce);
debounce = null;
}
debounce = setTimeout(function () {
debounce = null;
_this.contentElement.style.pointerEvents = 'auto';
}, 50);
}
var clusterIndex = _this.getCurrentClusterIndex();
if (_this.state.lastClusterIndex !== clusterIndex) {
_this.changeDOM();
}
_this.state.lastClusterIndex = clusterIndex;
};
}();
_this.resizeEventListener = function () {
var debounce = null;
return function () {
if (debounce) {
clearTimeout(debounce);
debounce = null;
}
debounce = setTimeout(function () {
var prevItemHeight = _this.state.itemHeight;
var current = _this.computeHeight();
if (current.itemHeight > 0 && prevItemHeight !== current.itemHeight) {
_this.state = _extends({}, _this.state, current);
_this.update(_this.rows);
}
}, 100);
};
}();
if (!(_this instanceof Clusterize)) {
var _ret;
return _ret = new Clusterize(options), _possibleConstructorReturn(_this, _ret);
}
_this.options = Object.keys(_this.options).reduce(function (acc, key) {
if (options[key] !== undefined) {
acc[key] = options[key];
} else {
acc[key] = _this.options[key];
}
return acc;
}, {});
_this.scrollElement = options.scrollElement;
_this.contentElement = options.contentElement;
// Keep focus on the scrolling content
if (!_this.contentElement.hasAttribute('tabindex')) {
_this.contentElement.setAttribute('tabindex', 0);
}
if (Array.isArray(options.rows)) {
_this.rows = options.rows;
} else {
_this.rows = [];
var nodes = _this.contentElement.children;
var length = nodes.length;
for (var i = 0; i < length; ++i) {
var node = nodes[i];
_this.rows.push(node.outerHTML || '');
}
}
// Remember scroll position
var scrollTop = _this.scrollElement.scrollTop;
_this.changeDOM();
// Restore scroll position
_this.scrollElement.scrollTop = scrollTop;
(0, _dom.addEventListener)(_this.scrollElement, 'scroll', _this.scrollEventListener);
(0, _dom.addEventListener)(window, 'resize', _this.resizeEventListener);
return _this;
}
Clusterize.prototype.destroy = function destroy(clean) {
(0, _dom.removeEventListener)(this.scrollElement, 'scroll', this.scrollEventListener);
(0, _dom.removeEventListener)(window, 'resize', this.resizeEventListener);
var rows = clean ? this.generateEmptyRow() : this.rows();
this.setContent(rows.join(''));
};
Clusterize.prototype.update = function update(rows) {
this.rows = (0, _ensureArray2['default'])(rows);
// Remember scroll position
var scrollTop = this.scrollElement.scrollTop;
if (this.rows.length * this.state.itemHeight < scrollTop) {
this.scrollElement.scrollTop = 0;
this.state.lastClusterIndex = 0;
}
this.changeDOM();
// Restore scroll position
this.scrollElement.scrollTop = scrollTop;
};
Clusterize.prototype.clear = function clear() {
this.rows = [];
this.update();
};
Clusterize.prototype.append = function append(rows) {
rows = (0, _ensureArray2['default'])(rows);
if (!rows.length) {
return;
}
this.rows = this.rows.concat(rows);
this.changeDOM();
};
Clusterize.prototype.prepend = function prepend(rows) {
rows = (0, _ensureArray2['default'])(rows);
if (!rows.length) {
return;
}
this.rows = rows.concat(this.rows);
this.changeDOM();
};
Clusterize.prototype.computeHeight = function computeHeight() {
if (!this.rows.length) {
return {
clusterHeight: 0,
blockHeight: this.state.blockHeight,
itemHeight: this.state.itemHeight
};
} else {
var nodes = this.contentElement.children;
var node = nodes[Math.floor(nodes.length / 2)];
var itemHeight = node.offsetHeight;
if (this.options.tag === 'tr' && (0, _dom.getElementStyle)(this.contentElement, 'borderCollapse') !== 'collapse') {
itemHeight += parseInt((0, _dom.getElementStyle)(this.contentElement, 'borderSpacing'), 10) || 0;
}
if (this.options.tag !== 'tr') {
var marginTop = parseInt((0, _dom.getElementStyle)(node, 'marginTop'), 10) || 0;
var marginBottom = parseInt((0, _dom.getElementStyle)(node, 'marginBottom'), 10) || 0;
itemHeight += Math.max(marginTop, marginBottom);
}
var blockHeight = itemHeight * this.options.rowsInBlock;
var clusterHeight = blockHeight * this.options.blocksInCluster;
return {
itemHeight: itemHeight,
blockHeight: blockHeight,
clusterHeight: clusterHeight
};
}
};
Clusterize.prototype.getCurrentClusterIndex = function getCurrentClusterIndex() {
var _state = this.state,
blockHeight = _state.blockHeight,
clusterHeight = _state.clusterHeight;
if (!blockHeight || !clusterHeight) {
return 0;
}
return Math.floor(this.scrollElement.scrollTop / (clusterHeight - blockHeight)) || 0;
};
Clusterize.prototype.generateEmptyRow = function generateEmptyRow() {
var _options = this.options,
tag = _options.tag,
emptyText = _options.emptyText,
emptyClass = _options.emptyClass;
if (!tag || !emptyText) {
return [];
}
var emptyRow = document.createElement(tag);
emptyRow.className = emptyClass;
if (tag === 'tr') {
var td = document.createElement('td');
td.colSpan = 100;
td.appendChild(document.createTextNode(emptyText));
emptyRow.appendChild(td);
} else {
emptyRow.appendChild(document.createTextNode(emptyText));
}
return [emptyRow.outerHTML];
};
Clusterize.prototype.renderExtraTag = function renderExtraTag(className, height) {
var tag = document.createElement(this.options.tag);
var prefix = 'infinite-tree-';
tag.className = [prefix + 'extra-row', prefix + className].join(' ');
if (height) {
tag.style.height = height + 'px';
}
return tag.outerHTML;
};
Clusterize.prototype.changeDOM = function changeDOM() {
if (!this.state.clusterHeight && this.rows.length > 0) {
if (ie && ie <= 9 && !this.options.tag) {
this.options.tag = this.rows[0].match(/<([^>\s/]*)/)[1].toLowerCase();
}
if (this.contentElement.children.length <= 1) {
this.cache.content = this.setContent(this.rows[0] + this.rows[0] + this.rows[0]);
}
if (!this.options.tag) {
this.options.tag = this.contentElement.children[0].tagName.toLowerCase();
}
this.state = _extends({}, this.state, this.computeHeight());
}
var topOffset = 0;
var bottomOffset = 0;
var rows = [];
if (this.rows.length < this.options.rowsInBlock) {
rows = this.rows.length > 0 ? this.rows : this.generateEmptyRow();
} else {
var rowsInCluster = this.options.rowsInBlock * this.options.blocksInCluster;
var clusterIndex = this.getCurrentClusterIndex();
var visibleStart = Math.max((rowsInCluster - this.options.rowsInBlock) * clusterIndex, 0);
var visibleEnd = visibleStart + rowsInCluster;
topOffset = Math.max(visibleStart * this.state.itemHeight, 0);
bottomOffset = Math.max((this.rows.length - visibleEnd) * this.state.itemHeight, 0);
// Returns a shallow copy of the rows selected from `visibleStart` to `visibleEnd` (`visibleEnd` not included).
rows = this.rows.slice(visibleStart, visibleEnd);
}
var content = rows.join('');
var contentChanged = this.checkChanges('content', content);
var topOffsetChanged = this.checkChanges('top', topOffset);
var bottomOffsetChanged = this.checkChanges('bottom', bottomOffset);
if (contentChanged || topOffsetChanged) {
var layout = [];
if (topOffset > 0) {
if (this.options.keepParity) {
layout.push(this.renderExtraTag('keep-parity'));
}
layout.push(this.renderExtraTag('top-space', topOffset));
}
layout.push(content);
if (bottomOffset > 0) {
layout.push(this.renderExtraTag('bottom-space', bottomOffset));
}
this.emit('clusterWillChange');
this.setContent(layout.join(''));
this.emit('clusterDidChange');
} else if (bottomOffsetChanged) {
this.contentElement.lastChild.style.height = bottomOffset + 'px';
}
};
Clusterize.prototype.setContent = function setContent(content) {
// For IE 9 and older versions
if (ie && ie <= 9 && this.options.tag === 'tr') {
var div = document.createElement('div');
div.innerHTML = '<table><tbody>' + content + '</tbody></table>';
var lastChild = this.contentElement.lastChild;
while (lastChild) {
this.contentElement.removeChild(lastChild);
lastChild = this.contentElement.lastChild;
}
var rowsNodes = this.getChildNodes(div.firstChild.firstChild);
while (rowsNodes.length) {
this.contentElement.appendChild(rowsNodes.shift());
}
} else {
this.contentElement.innerHTML = content;
}
};
Clusterize.prototype.getChildNodes = function getChildNodes(tag) {
var childNodes = tag.children;
var nodes = [];
var length = childNodes.length;
for (var i = 0; i < length; i++) {
nodes.push(childNodes[i]);
}
return nodes;
};
Clusterize.prototype.checkChanges = function checkChanges(type, value) {
var changed = value !== this.cache[type];
this.cache[type] = value;
return changed;
};
return Clusterize;
}(_events.EventEmitter);
exports['default'] = Clusterize;