react-pivottable
Version:
A React-based pivot table
584 lines (523 loc) • 25.5 kB
JavaScript
'use strict';
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; }; }();
var _react = require('react');
var _react2 = _interopRequireDefault(_react);
var _propTypes = require('prop-types');
var _propTypes2 = _interopRequireDefault(_propTypes);
var _immutabilityHelper = require('immutability-helper');
var _immutabilityHelper2 = _interopRequireDefault(_immutabilityHelper);
var _Utilities = require('./Utilities');
var _PivotTable = require('./PivotTable');
var _PivotTable2 = _interopRequireDefault(_PivotTable);
var _reactSortablejs = require('react-sortablejs');
var _reactSortablejs2 = _interopRequireDefault(_reactSortablejs);
var _reactDraggable = require('react-draggable');
var _reactDraggable2 = _interopRequireDefault(_reactDraggable);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return 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; }
/* eslint-disable react/prop-types */
// eslint can't see inherited propTypes!
var DraggableAttribute = function (_React$Component) {
_inherits(DraggableAttribute, _React$Component);
function DraggableAttribute(props) {
_classCallCheck(this, DraggableAttribute);
var _this = _possibleConstructorReturn(this, (DraggableAttribute.__proto__ || Object.getPrototypeOf(DraggableAttribute)).call(this, props));
_this.state = { open: false, top: 0, left: 0, filterText: '' };
return _this;
}
_createClass(DraggableAttribute, [{
key: 'toggleValue',
value: function toggleValue(value) {
if (value in this.props.valueFilter) {
this.props.removeValuesFromFilter(this.props.name, [value]);
} else {
this.props.addValuesToFilter(this.props.name, [value]);
}
}
}, {
key: 'matchesFilter',
value: function matchesFilter(x) {
return x.toLowerCase().trim().includes(this.state.filterText.toLowerCase().trim());
}
}, {
key: 'selectOnly',
value: function selectOnly(e, value) {
e.stopPropagation();
this.props.setValuesInFilter(this.props.name, Object.keys(this.props.attrValues).filter(function (y) {
return y !== value;
}));
}
}, {
key: 'getFilterBox',
value: function getFilterBox() {
var _this2 = this;
var showMenu = Object.keys(this.props.attrValues).length < this.props.menuLimit;
var values = Object.keys(this.props.attrValues);
var shown = values.filter(this.matchesFilter.bind(this)).sort(this.props.sorter);
return _react2.default.createElement(
_reactDraggable2.default,
{ handle: '.pvtDragHandle' },
_react2.default.createElement(
'div',
{ className: 'pvtFilterBox', style: {
display: 'block', cursor: 'initial',
top: this.state.top + 'px', left: this.state.left + 'px' }
},
_react2.default.createElement(
'a',
{ onClick: function onClick() {
return _this2.setState({ open: false });
},
className: 'pvtCloseX'
},
'\xD7'
),
_react2.default.createElement(
'span',
{ className: 'pvtDragHandle' },
'\u2630'
),
_react2.default.createElement(
'h4',
null,
this.props.name
),
showMenu || _react2.default.createElement(
'p',
null,
'(too many values to show)'
),
showMenu && _react2.default.createElement(
'p',
null,
_react2.default.createElement('input', { type: 'text', placeholder: 'Filter values', className: 'pvtSearch',
value: this.state.filterText,
onChange: function onChange(e) {
return _this2.setState({ filterText: e.target.value });
}
}),
_react2.default.createElement('br', null),
_react2.default.createElement(
'button',
{ type: 'button',
onClick: function onClick() {
return _this2.props.removeValuesFromFilter(_this2.props.name, Object.keys(_this2.props.attrValues).filter(_this2.matchesFilter.bind(_this2)));
}
},
'Select ',
values.length === shown.length ? 'All' : shown.length
),
' ',
_react2.default.createElement(
'button',
{ type: 'button',
onClick: function onClick() {
return _this2.props.addValuesToFilter(_this2.props.name, Object.keys(_this2.props.attrValues).filter(_this2.matchesFilter.bind(_this2)));
}
},
'Deselect ',
values.length === shown.length ? 'All' : shown.length
)
),
showMenu && _react2.default.createElement(
'div',
{ className: 'pvtCheckContainer' },
shown.map(function (x) {
return _react2.default.createElement(
'p',
{ key: x, onClick: function onClick() {
return _this2.toggleValue(x);
},
className: x in _this2.props.valueFilter ? '' : 'selected'
},
_react2.default.createElement(
'a',
{ className: 'pvtOnly',
onClick: function onClick(e) {
return _this2.selectOnly(e, x);
}
},
'only'
),
_react2.default.createElement(
'a',
{ className: 'pvtOnlySpacer' },
'\xA0'
),
x === '' ? _react2.default.createElement(
'em',
null,
'null'
) : x
);
})
)
)
);
}
}, {
key: 'toggleFilterBox',
value: function toggleFilterBox(event) {
var bodyRect = document.body.getBoundingClientRect();
var rect = event.nativeEvent.target.getBoundingClientRect();
this.setState({
open: !this.state.open,
top: 10 + rect.top - bodyRect.top,
left: 10 + rect.left - bodyRect.left });
}
}, {
key: 'render',
value: function render() {
var filtered = Object.keys(this.props.valueFilter).length !== 0 ? 'pvtFilteredAttribute' : '';
return _react2.default.createElement(
'li',
{ 'data-id': this.props.name },
_react2.default.createElement(
'span',
{ className: 'pvtAttr ' + filtered },
this.props.name,
_react2.default.createElement(
'span',
{ className: 'pvtTriangle', onClick: this.toggleFilterBox.bind(this) },
' \u25BE'
)
),
this.state.open ? this.getFilterBox() : null
);
}
}]);
return DraggableAttribute;
}(_react2.default.Component);
DraggableAttribute.defaultProps = {
valueFilter: {}
};
DraggableAttribute.propTypes = {
name: _propTypes2.default.string.isRequired,
addValuesToFilter: _propTypes2.default.func.isRequired,
removeValuesFromFilter: _propTypes2.default.func.isRequired,
attrValues: _propTypes2.default.objectOf(_propTypes2.default.number).isRequired,
valueFilter: _propTypes2.default.objectOf(_propTypes2.default.bool),
sorter: _propTypes2.default.func.isRequired,
menuLimit: _propTypes2.default.number
};
var PivotTableUI = function (_React$PureComponent) {
_inherits(PivotTableUI, _React$PureComponent);
function PivotTableUI(props) {
_classCallCheck(this, PivotTableUI);
var _this3 = _possibleConstructorReturn(this, (PivotTableUI.__proto__ || Object.getPrototypeOf(PivotTableUI)).call(this, props));
_this3.state = { unusedOrder: [] };
return _this3;
}
_createClass(PivotTableUI, [{
key: 'componentWillMount',
value: function componentWillMount() {
this.materializeInput(this.props.data);
}
}, {
key: 'componentWillUpdate',
value: function componentWillUpdate(nextProps) {
this.materializeInput(nextProps.data);
}
}, {
key: 'materializeInput',
value: function materializeInput(nextData) {
if (this.data === nextData) {
return;
}
this.data = nextData;
var attrValues = {};
var materializedInput = [];
var recordsProcessed = 0;
_Utilities.PivotData.forEachRecord(this.data, this.props.derivedAttributes, function (record) {
materializedInput.push(record);
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;
try {
for (var _iterator = Object.keys(record)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
var _attr = _step.value;
if (!(_attr in attrValues)) {
attrValues[_attr] = {};
if (recordsProcessed > 0) {
attrValues[_attr].null = recordsProcessed;
}
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}
for (var attr in attrValues) {
var value = attr in record ? record[attr] : 'null';
if (!(value in attrValues[attr])) {
attrValues[attr][value] = 0;
}
attrValues[attr][value]++;
}
recordsProcessed++;
});
this.materializedInput = materializedInput;
this.attrValues = attrValues;
}
}, {
key: 'sendPropUpdate',
value: function sendPropUpdate(command) {
this.props.onChange((0, _immutabilityHelper2.default)(this.props, command));
}
}, {
key: 'propUpdater',
value: function propUpdater(key) {
var _this4 = this;
return function (value) {
return _this4.sendPropUpdate(_defineProperty({}, key, { $set: value }));
};
}
}, {
key: 'setValuesInFilter',
value: function setValuesInFilter(attribute, values) {
this.sendPropUpdate({ valueFilter: _defineProperty({}, attribute, { $set: values.reduce(function (r, v) {
r[v] = true;return r;
}, {}) }) });
}
}, {
key: 'addValuesToFilter',
value: function addValuesToFilter(attribute, values) {
if (attribute in this.props.valueFilter) {
this.sendPropUpdate({ valueFilter: _defineProperty({}, attribute, values.reduce(function (r, v) {
r[v] = { $set: true };return r;
}, {})) });
} else {
this.setValuesInFilter(attribute, values);
}
}
}, {
key: 'removeValuesFromFilter',
value: function removeValuesFromFilter(attribute, values) {
this.sendPropUpdate({ valueFilter: _defineProperty({}, attribute, { $unset: values }) });
}
}, {
key: 'makeDnDCell',
value: function makeDnDCell(items, onChange, classes) {
var _this5 = this;
return _react2.default.createElement(
_reactSortablejs2.default,
{
options: {
group: 'shared', ghostClass: 'pvtPlaceholder',
filter: '.pvtFilterBox', preventOnFilter: false
},
tag: 'td', className: classes, onChange: onChange
},
items.map(function (x) {
return _react2.default.createElement(DraggableAttribute, { name: x, key: x,
attrValues: _this5.attrValues[x],
valueFilter: _this5.props.valueFilter[x] || {},
sorter: (0, _Utilities.getSort)(_this5.props.sorters, x),
menuLimit: _this5.props.menuLimit,
setValuesInFilter: _this5.setValuesInFilter.bind(_this5),
addValuesToFilter: _this5.addValuesToFilter.bind(_this5),
removeValuesFromFilter: _this5.removeValuesFromFilter.bind(_this5)
});
})
);
}
}, {
key: 'render',
value: function render() {
var _this6 = this;
var numValsAllowed = this.props.aggregators[this.props.aggregatorName]([])().numInputs || 0;
var rendererName = this.props.rendererName in this.props.renderers ? this.props.rendererName : Object.keys(this.props.renderers)[0];
var rendererCell = _react2.default.createElement(
'td',
{ className: 'pvtRenderers' },
_react2.default.createElement(
'select',
{ value: rendererName,
onChange: function onChange(_ref) {
var value = _ref.target.value;
return _this6.propUpdater('rendererName')(value);
}
},
Object.keys(this.props.renderers).map(function (r) {
return _react2.default.createElement(
'option',
{ value: r, key: r },
r
);
})
)
);
var sortIcons = {
key_a_to_z: { rowSymbol: '↕', colSymbol: '↔', next: 'value_a_to_z' },
value_a_to_z: { rowSymbol: '↓', colSymbol: '→', next: 'value_z_to_a' },
value_z_to_a: { rowSymbol: '↑', colSymbol: '←', next: 'key_a_to_z' }
};
var aggregatorCell = _react2.default.createElement(
'td',
{ className: 'pvtVals' },
_react2.default.createElement(
'select',
{ value: this.props.aggregatorName,
onChange: function onChange(_ref2) {
var value = _ref2.target.value;
return _this6.propUpdater('aggregatorName')(value);
}
},
Object.keys(this.props.aggregators).map(function (n) {
return _react2.default.createElement(
'option',
{ key: 'agg' + n, value: n },
n
);
})
),
_react2.default.createElement(
'a',
{ role: 'button', className: 'pvtRowOrder', onClick: function onClick() {
return _this6.propUpdater('rowOrder')(sortIcons[_this6.props.rowOrder].next);
}
},
sortIcons[this.props.rowOrder].rowSymbol
),
_react2.default.createElement(
'a',
{ role: 'button', className: 'pvtColOrder', onClick: function onClick() {
return _this6.propUpdater('colOrder')(sortIcons[_this6.props.colOrder].next);
}
},
sortIcons[this.props.colOrder].colSymbol
),
numValsAllowed > 0 && _react2.default.createElement('br', null),
new Array(numValsAllowed).fill().map(function (n, i) {
return _react2.default.createElement(
'select',
{ value: _this6.props.vals[i], key: 'val' + i,
onChange: function onChange(_ref3) {
var value = _ref3.target.value;
return _this6.sendPropUpdate({ vals: { $splice: [[i, 1, value]] } });
}
},
_react2.default.createElement('option', { key: 'none' + i, value: '' }),
Object.keys(_this6.attrValues).filter(function (e) {
return !_this6.props.hiddenAttributes.includes(e) && !_this6.props.hiddenFromAggregators.includes(e);
}).map(function (v, j) {
return _react2.default.createElement(
'option',
{ key: i + '-' + j, value: v },
v
);
})
);
})
);
var unusedAttrs = Object.keys(this.attrValues).filter(function (e) {
return !_this6.props.rows.includes(e) && !_this6.props.cols.includes(e) && !_this6.props.hiddenAttributes.includes(e) && !_this6.props.hiddenFromDragDrop.includes(e);
}).sort((0, _Utilities.sortAs)(this.state.unusedOrder));
var unusedLength = unusedAttrs.reduce(function (r, e) {
return r + e.length;
}, 0);
var horizUnused = unusedLength < this.props.unusedOrientationCutoff;
var unusedAttrsCell = this.makeDnDCell(unusedAttrs, function (order) {
return _this6.setState({ unusedOrder: order });
}, 'pvtAxisContainer pvtUnused ' + (horizUnused ? 'pvtHorizList' : 'pvtVertList'));
var colAttrs = this.props.cols.filter(function (e) {
return !_this6.props.hiddenAttributes.includes(e) && !_this6.props.hiddenFromDragDrop.includes(e);
});
var colAttrsCell = this.makeDnDCell(colAttrs, this.propUpdater('cols'), 'pvtAxisContainer pvtHorizList pvtCols');
var rowAttrs = this.props.rows.filter(function (e) {
return !_this6.props.hiddenAttributes.includes(e) && !_this6.props.hiddenFromDragDrop.includes(e);
});
var rowAttrsCell = this.makeDnDCell(rowAttrs, this.propUpdater('rows'), 'pvtAxisContainer pvtVertList pvtRows');
var outputCell = _react2.default.createElement(
'td',
{ valign: 'top' },
_react2.default.createElement(_PivotTable2.default, (0, _immutabilityHelper2.default)(this.props, { data: { $set: this.materializedInput } }))
);
if (horizUnused) {
return _react2.default.createElement(
'table',
{ className: 'pvtUi' },
_react2.default.createElement(
'tbody',
null,
_react2.default.createElement(
'tr',
null,
rendererCell,
unusedAttrsCell
),
_react2.default.createElement(
'tr',
null,
aggregatorCell,
colAttrsCell
),
_react2.default.createElement(
'tr',
null,
rowAttrsCell,
outputCell
)
)
);
}
return _react2.default.createElement(
'table',
{ className: 'pvtUi' },
_react2.default.createElement(
'tbody',
null,
_react2.default.createElement(
'tr',
null,
rendererCell,
aggregatorCell,
colAttrsCell
),
_react2.default.createElement(
'tr',
null,
unusedAttrsCell,
rowAttrsCell,
outputCell
)
)
);
}
}]);
return PivotTableUI;
}(_react2.default.PureComponent);
PivotTableUI.propTypes = Object.assign({}, _PivotTable2.default.propTypes, {
onChange: _propTypes2.default.func.isRequired,
hiddenAttributes: _propTypes2.default.arrayOf(_propTypes2.default.string),
hiddenFromAggregators: _propTypes2.default.arrayOf(_propTypes2.default.string),
hiddenFromDragDrop: _propTypes2.default.arrayOf(_propTypes2.default.string),
unusedOrientationCutoff: _propTypes2.default.number,
menuLimit: _propTypes2.default.number
});
PivotTableUI.defaultProps = Object.assign({}, _PivotTable2.default.defaultProps, {
hiddenAttributes: [],
hiddenFromAggregators: [],
hiddenFromDragDrop: [],
unusedOrientationCutoff: 85,
menuLimit: 500
});
exports.default = PivotTableUI;
module.exports = exports['default'];
//# sourceMappingURL=PivotTableUI.js.map