UNPKG

25 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, "__esModule", {
4 value: true
5});
6
7var _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; }; }();
8
9var _react = require('react');
10
11var _react2 = _interopRequireDefault(_react);
12
13var _propTypes = require('prop-types');
14
15var _propTypes2 = _interopRequireDefault(_propTypes);
16
17var _immutabilityHelper = require('immutability-helper');
18
19var _immutabilityHelper2 = _interopRequireDefault(_immutabilityHelper);
20
21var _Utilities = require('./Utilities');
22
23var _PivotTable = require('./PivotTable');
24
25var _PivotTable2 = _interopRequireDefault(_PivotTable);
26
27var _reactSortablejs = require('react-sortablejs');
28
29var _reactSortablejs2 = _interopRequireDefault(_reactSortablejs);
30
31var _reactDraggable = require('react-draggable');
32
33var _reactDraggable2 = _interopRequireDefault(_reactDraggable);
34
35function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
36
37function _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; }
38
39function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
40
41function _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; }
42
43function _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; }
44
45/* eslint-disable react/prop-types */
46// eslint can't see inherited propTypes!
47
48var DraggableAttribute = function (_React$Component) {
49 _inherits(DraggableAttribute, _React$Component);
50
51 function DraggableAttribute(props) {
52 _classCallCheck(this, DraggableAttribute);
53
54 var _this = _possibleConstructorReturn(this, (DraggableAttribute.__proto__ || Object.getPrototypeOf(DraggableAttribute)).call(this, props));
55
56 _this.state = { open: false, top: 0, left: 0, filterText: '' };
57 return _this;
58 }
59
60 _createClass(DraggableAttribute, [{
61 key: 'toggleValue',
62 value: function toggleValue(value) {
63 if (value in this.props.valueFilter) {
64 this.props.removeValuesFromFilter(this.props.name, [value]);
65 } else {
66 this.props.addValuesToFilter(this.props.name, [value]);
67 }
68 }
69 }, {
70 key: 'matchesFilter',
71 value: function matchesFilter(x) {
72 return x.toLowerCase().trim().includes(this.state.filterText.toLowerCase().trim());
73 }
74 }, {
75 key: 'selectOnly',
76 value: function selectOnly(e, value) {
77 e.stopPropagation();
78 this.props.setValuesInFilter(this.props.name, Object.keys(this.props.attrValues).filter(function (y) {
79 return y !== value;
80 }));
81 }
82 }, {
83 key: 'getFilterBox',
84 value: function getFilterBox() {
85 var _this2 = this;
86
87 var showMenu = Object.keys(this.props.attrValues).length < this.props.menuLimit;
88
89 var values = Object.keys(this.props.attrValues);
90 var shown = values.filter(this.matchesFilter.bind(this)).sort(this.props.sorter);
91
92 return _react2.default.createElement(
93 _reactDraggable2.default,
94 { handle: '.pvtDragHandle' },
95 _react2.default.createElement(
96 'div',
97 {
98 className: 'pvtFilterBox',
99 style: {
100 display: 'block',
101 cursor: 'initial',
102 zIndex: this.props.zIndex,
103 top: this.state.top + 'px',
104 left: this.state.left + 'px'
105 },
106 onClick: function onClick() {
107 return _this2.props.moveFilterBoxToTop(_this2.props.name);
108 }
109 },
110 _react2.default.createElement(
111 'a',
112 { onClick: function onClick() {
113 return _this2.setState({ open: false });
114 }, className: 'pvtCloseX' },
115 '\xD7'
116 ),
117 _react2.default.createElement(
118 'span',
119 { className: 'pvtDragHandle' },
120 '\u2630'
121 ),
122 _react2.default.createElement(
123 'h4',
124 null,
125 this.props.name
126 ),
127 showMenu || _react2.default.createElement(
128 'p',
129 null,
130 '(too many values to show)'
131 ),
132 showMenu && _react2.default.createElement(
133 'p',
134 null,
135 _react2.default.createElement('input', {
136 type: 'text',
137 placeholder: 'Filter values',
138 className: 'pvtSearch',
139 value: this.state.filterText,
140 onChange: function onChange(e) {
141 return _this2.setState({
142 filterText: e.target.value
143 });
144 }
145 }),
146 _react2.default.createElement('br', null),
147 _react2.default.createElement(
148 'a',
149 {
150 role: 'button',
151 className: 'pvtButton',
152 onClick: function onClick() {
153 return _this2.props.removeValuesFromFilter(_this2.props.name, Object.keys(_this2.props.attrValues).filter(_this2.matchesFilter.bind(_this2)));
154 }
155 },
156 'Select ',
157 values.length === shown.length ? 'All' : shown.length
158 ),
159 ' ',
160 _react2.default.createElement(
161 'a',
162 {
163 role: 'button',
164 className: 'pvtButton',
165 onClick: function onClick() {
166 return _this2.props.addValuesToFilter(_this2.props.name, Object.keys(_this2.props.attrValues).filter(_this2.matchesFilter.bind(_this2)));
167 }
168 },
169 'Deselect ',
170 values.length === shown.length ? 'All' : shown.length
171 )
172 ),
173 showMenu && _react2.default.createElement(
174 'div',
175 { className: 'pvtCheckContainer' },
176 shown.map(function (x) {
177 return _react2.default.createElement(
178 'p',
179 {
180 key: x,
181 onClick: function onClick() {
182 return _this2.toggleValue(x);
183 },
184 className: x in _this2.props.valueFilter ? '' : 'selected'
185 },
186 _react2.default.createElement(
187 'a',
188 { className: 'pvtOnly', onClick: function onClick(e) {
189 return _this2.selectOnly(e, x);
190 } },
191 'only'
192 ),
193 _react2.default.createElement(
194 'a',
195 { className: 'pvtOnlySpacer' },
196 '\xA0'
197 ),
198 x === '' ? _react2.default.createElement(
199 'em',
200 null,
201 'null'
202 ) : x
203 );
204 })
205 )
206 )
207 );
208 }
209 }, {
210 key: 'toggleFilterBox',
211 value: function toggleFilterBox(event) {
212 var bodyRect = document.body.getBoundingClientRect();
213 var rect = event.nativeEvent.target.getBoundingClientRect();
214 this.setState({
215 open: !this.state.open,
216 top: 10 + rect.top - bodyRect.top,
217 left: 10 + rect.left - bodyRect.left
218 });
219 this.props.moveFilterBoxToTop(this.props.name);
220 }
221 }, {
222 key: 'render',
223 value: function render() {
224 var filtered = Object.keys(this.props.valueFilter).length !== 0 ? 'pvtFilteredAttribute' : '';
225 return _react2.default.createElement(
226 'li',
227 { 'data-id': this.props.name },
228 _react2.default.createElement(
229 'span',
230 { className: 'pvtAttr ' + filtered },
231 this.props.name,
232 _react2.default.createElement(
233 'span',
234 {
235 className: 'pvtTriangle',
236 onClick: this.toggleFilterBox.bind(this)
237 },
238 ' ',
239 '\u25BE'
240 )
241 ),
242 this.state.open ? this.getFilterBox() : null
243 );
244 }
245 }]);
246
247 return DraggableAttribute;
248}(_react2.default.Component);
249
250DraggableAttribute.defaultProps = {
251 valueFilter: {}
252};
253
254DraggableAttribute.propTypes = {
255 name: _propTypes2.default.string.isRequired,
256 addValuesToFilter: _propTypes2.default.func.isRequired,
257 removeValuesFromFilter: _propTypes2.default.func.isRequired,
258 attrValues: _propTypes2.default.objectOf(_propTypes2.default.number).isRequired,
259 valueFilter: _propTypes2.default.objectOf(_propTypes2.default.bool),
260 moveFilterBoxToTop: _propTypes2.default.func.isRequired,
261 sorter: _propTypes2.default.func.isRequired,
262 menuLimit: _propTypes2.default.number,
263 zIndex: _propTypes2.default.number
264};
265
266var Dropdown = function (_React$PureComponent) {
267 _inherits(Dropdown, _React$PureComponent);
268
269 function Dropdown() {
270 _classCallCheck(this, Dropdown);
271
272 return _possibleConstructorReturn(this, (Dropdown.__proto__ || Object.getPrototypeOf(Dropdown)).apply(this, arguments));
273 }
274
275 _createClass(Dropdown, [{
276 key: 'render',
277 value: function render() {
278 var _this4 = this;
279
280 return _react2.default.createElement(
281 'div',
282 { className: 'pvtDropdown', style: { zIndex: this.props.zIndex } },
283 _react2.default.createElement(
284 'div',
285 {
286 onClick: function onClick(e) {
287 e.stopPropagation();
288 _this4.props.toggle();
289 },
290 className: 'pvtDropdownValue pvtDropdownCurrent ' + (this.props.open ? 'pvtDropdownCurrentOpen' : ''),
291 role: 'button'
292 },
293 _react2.default.createElement(
294 'div',
295 { className: 'pvtDropdownIcon' },
296 this.props.open ? '×' : '▾'
297 ),
298 this.props.current || _react2.default.createElement(
299 'span',
300 null,
301 '\xA0'
302 )
303 ),
304 this.props.open && _react2.default.createElement(
305 'div',
306 { className: 'pvtDropdownMenu' },
307 this.props.values.map(function (r) {
308 return _react2.default.createElement(
309 'div',
310 {
311 key: r,
312 role: 'button',
313 onClick: function onClick(e) {
314 e.stopPropagation();
315 if (_this4.props.current === r) {
316 _this4.props.toggle();
317 } else {
318 _this4.props.setValue(r);
319 }
320 },
321 className: 'pvtDropdownValue ' + (r === _this4.props.current ? 'pvtDropdownActiveValue' : '')
322 },
323 r
324 );
325 })
326 )
327 );
328 }
329 }]);
330
331 return Dropdown;
332}(_react2.default.PureComponent);
333
334var PivotTableUI = function (_React$PureComponent2) {
335 _inherits(PivotTableUI, _React$PureComponent2);
336
337 function PivotTableUI(props) {
338 _classCallCheck(this, PivotTableUI);
339
340 var _this5 = _possibleConstructorReturn(this, (PivotTableUI.__proto__ || Object.getPrototypeOf(PivotTableUI)).call(this, props));
341
342 _this5.state = {
343 unusedOrder: [],
344 zIndices: {},
345 maxZIndex: 1000,
346 openDropdown: false
347 };
348 return _this5;
349 }
350
351 _createClass(PivotTableUI, [{
352 key: 'componentWillMount',
353 value: function componentWillMount() {
354 this.materializeInput(this.props.data);
355 }
356 }, {
357 key: 'componentWillUpdate',
358 value: function componentWillUpdate(nextProps) {
359 this.materializeInput(nextProps.data);
360 }
361 }, {
362 key: 'materializeInput',
363 value: function materializeInput(nextData) {
364 if (this.data === nextData) {
365 return;
366 }
367 this.data = nextData;
368 var attrValues = {};
369 var materializedInput = [];
370 var recordsProcessed = 0;
371 _Utilities.PivotData.forEachRecord(this.data, this.props.derivedAttributes, function (record) {
372 materializedInput.push(record);
373 var _iteratorNormalCompletion = true;
374 var _didIteratorError = false;
375 var _iteratorError = undefined;
376
377 try {
378 for (var _iterator = Object.keys(record)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
379 var attr = _step.value;
380
381 if (!(attr in attrValues)) {
382 attrValues[attr] = {};
383 if (recordsProcessed > 0) {
384 attrValues[attr].null = recordsProcessed;
385 }
386 }
387 }
388 } catch (err) {
389 _didIteratorError = true;
390 _iteratorError = err;
391 } finally {
392 try {
393 if (!_iteratorNormalCompletion && _iterator.return) {
394 _iterator.return();
395 }
396 } finally {
397 if (_didIteratorError) {
398 throw _iteratorError;
399 }
400 }
401 }
402
403 for (var _attr in attrValues) {
404 var value = _attr in record ? record[_attr] : 'null';
405 if (!(value in attrValues[_attr])) {
406 attrValues[_attr][value] = 0;
407 }
408 attrValues[_attr][value]++;
409 }
410 recordsProcessed++;
411 });
412
413 this.materializedInput = materializedInput;
414 this.attrValues = attrValues;
415 }
416 }, {
417 key: 'sendPropUpdate',
418 value: function sendPropUpdate(command) {
419 this.props.onChange((0, _immutabilityHelper2.default)(this.props, command));
420 }
421 }, {
422 key: 'propUpdater',
423 value: function propUpdater(key) {
424 var _this6 = this;
425
426 return function (value) {
427 return _this6.sendPropUpdate(_defineProperty({}, key, { $set: value }));
428 };
429 }
430 }, {
431 key: 'setValuesInFilter',
432 value: function setValuesInFilter(attribute, values) {
433 this.sendPropUpdate({
434 valueFilter: _defineProperty({}, attribute, {
435 $set: values.reduce(function (r, v) {
436 r[v] = true;
437 return r;
438 }, {})
439 })
440 });
441 }
442 }, {
443 key: 'addValuesToFilter',
444 value: function addValuesToFilter(attribute, values) {
445 if (attribute in this.props.valueFilter) {
446 this.sendPropUpdate({
447 valueFilter: _defineProperty({}, attribute, values.reduce(function (r, v) {
448 r[v] = { $set: true };
449 return r;
450 }, {}))
451 });
452 } else {
453 this.setValuesInFilter(attribute, values);
454 }
455 }
456 }, {
457 key: 'removeValuesFromFilter',
458 value: function removeValuesFromFilter(attribute, values) {
459 this.sendPropUpdate({
460 valueFilter: _defineProperty({}, attribute, { $unset: values })
461 });
462 }
463 }, {
464 key: 'moveFilterBoxToTop',
465 value: function moveFilterBoxToTop(attribute) {
466 this.setState((0, _immutabilityHelper2.default)(this.state, {
467 maxZIndex: { $set: this.state.maxZIndex + 1 },
468 zIndices: _defineProperty({}, attribute, { $set: this.state.maxZIndex + 1 })
469 }));
470 }
471 }, {
472 key: 'isOpen',
473 value: function isOpen(dropdown) {
474 return this.state.openDropdown === dropdown;
475 }
476 }, {
477 key: 'makeDnDCell',
478 value: function makeDnDCell(items, onChange, classes) {
479 var _this7 = this;
480
481 return _react2.default.createElement(
482 _reactSortablejs2.default,
483 {
484 options: {
485 group: 'shared',
486 ghostClass: 'pvtPlaceholder',
487 filter: '.pvtFilterBox',
488 preventOnFilter: false
489 },
490 tag: 'td',
491 className: classes,
492 onChange: onChange
493 },
494 items.map(function (x) {
495 return _react2.default.createElement(DraggableAttribute, {
496 name: x,
497 key: x,
498 attrValues: _this7.attrValues[x],
499 valueFilter: _this7.props.valueFilter[x] || {},
500 sorter: (0, _Utilities.getSort)(_this7.props.sorters, x),
501 menuLimit: _this7.props.menuLimit,
502 setValuesInFilter: _this7.setValuesInFilter.bind(_this7),
503 addValuesToFilter: _this7.addValuesToFilter.bind(_this7),
504 moveFilterBoxToTop: _this7.moveFilterBoxToTop.bind(_this7),
505 removeValuesFromFilter: _this7.removeValuesFromFilter.bind(_this7),
506 zIndex: _this7.state.zIndices[x] || _this7.state.maxZIndex
507 });
508 })
509 );
510 }
511 }, {
512 key: 'render',
513 value: function render() {
514 var _this8 = this;
515
516 var numValsAllowed = this.props.aggregators[this.props.aggregatorName]([])().numInputs || 0;
517
518 var rendererName = this.props.rendererName in this.props.renderers ? this.props.rendererName : Object.keys(this.props.renderers)[0];
519
520 var rendererCell = _react2.default.createElement(
521 'td',
522 { className: 'pvtRenderers' },
523 _react2.default.createElement(Dropdown, {
524 current: rendererName,
525 values: Object.keys(this.props.renderers),
526 open: this.isOpen('renderer'),
527 zIndex: this.isOpen('renderer') ? this.state.maxZIndex + 1 : 1,
528 toggle: function toggle() {
529 return _this8.setState({
530 openDropdown: _this8.isOpen('renderer') ? false : 'renderer'
531 });
532 },
533 setValue: this.propUpdater('rendererName')
534 })
535 );
536
537 var sortIcons = {
538 key_a_to_z: {
539 rowSymbol: '↕',
540 colSymbol: '↔',
541 next: 'value_a_to_z'
542 },
543 value_a_to_z: {
544 rowSymbol: '↓',
545 colSymbol: '→',
546 next: 'value_z_to_a'
547 },
548 value_z_to_a: { rowSymbol: '↑', colSymbol: '←', next: 'key_a_to_z' }
549 };
550
551 var aggregatorCell = _react2.default.createElement(
552 'td',
553 { className: 'pvtVals' },
554 _react2.default.createElement(Dropdown, {
555 current: this.props.aggregatorName,
556 values: Object.keys(this.props.aggregators),
557 open: this.isOpen('aggregators'),
558 zIndex: this.isOpen('aggregators') ? this.state.maxZIndex + 1 : 1,
559 toggle: function toggle() {
560 return _this8.setState({
561 openDropdown: _this8.isOpen('aggregators') ? false : 'aggregators'
562 });
563 },
564 setValue: this.propUpdater('aggregatorName')
565 }),
566 _react2.default.createElement(
567 'a',
568 {
569 role: 'button',
570 className: 'pvtRowOrder',
571 onClick: function onClick() {
572 return _this8.propUpdater('rowOrder')(sortIcons[_this8.props.rowOrder].next);
573 }
574 },
575 sortIcons[this.props.rowOrder].rowSymbol
576 ),
577 _react2.default.createElement(
578 'a',
579 {
580 role: 'button',
581 className: 'pvtColOrder',
582 onClick: function onClick() {
583 return _this8.propUpdater('colOrder')(sortIcons[_this8.props.colOrder].next);
584 }
585 },
586 sortIcons[this.props.colOrder].colSymbol
587 ),
588 numValsAllowed > 0 && _react2.default.createElement('br', null),
589 new Array(numValsAllowed).fill().map(function (n, i) {
590 return [_react2.default.createElement(Dropdown, {
591 key: i,
592 current: _this8.props.vals[i],
593 values: Object.keys(_this8.attrValues).filter(function (e) {
594 return !_this8.props.hiddenAttributes.includes(e) && !_this8.props.hiddenFromAggregators.includes(e);
595 }),
596 open: _this8.isOpen('val' + i),
597 zIndex: _this8.isOpen('val' + i) ? _this8.state.maxZIndex + 1 : 1,
598 toggle: function toggle() {
599 return _this8.setState({
600 openDropdown: _this8.isOpen('val' + i) ? false : 'val' + i
601 });
602 },
603 setValue: function setValue(value) {
604 return _this8.sendPropUpdate({
605 vals: { $splice: [[i, 1, value]] }
606 });
607 }
608 }), i + 1 !== numValsAllowed ? _react2.default.createElement('br', { key: 'br' + i }) : null];
609 })
610 );
611
612 var unusedAttrs = Object.keys(this.attrValues).filter(function (e) {
613 return !_this8.props.rows.includes(e) && !_this8.props.cols.includes(e) && !_this8.props.hiddenAttributes.includes(e) && !_this8.props.hiddenFromDragDrop.includes(e);
614 }).sort((0, _Utilities.sortAs)(this.state.unusedOrder));
615
616 var unusedLength = unusedAttrs.reduce(function (r, e) {
617 return r + e.length;
618 }, 0);
619 var horizUnused = unusedLength < this.props.unusedOrientationCutoff;
620
621 var unusedAttrsCell = this.makeDnDCell(unusedAttrs, function (order) {
622 return _this8.setState({ unusedOrder: order });
623 }, 'pvtAxisContainer pvtUnused ' + (horizUnused ? 'pvtHorizList' : 'pvtVertList'));
624
625 var colAttrs = this.props.cols.filter(function (e) {
626 return !_this8.props.hiddenAttributes.includes(e) && !_this8.props.hiddenFromDragDrop.includes(e);
627 });
628
629 var colAttrsCell = this.makeDnDCell(colAttrs, this.propUpdater('cols'), 'pvtAxisContainer pvtHorizList pvtCols');
630
631 var rowAttrs = this.props.rows.filter(function (e) {
632 return !_this8.props.hiddenAttributes.includes(e) && !_this8.props.hiddenFromDragDrop.includes(e);
633 });
634 var rowAttrsCell = this.makeDnDCell(rowAttrs, this.propUpdater('rows'), 'pvtAxisContainer pvtVertList pvtRows');
635 var outputCell = _react2.default.createElement(
636 'td',
637 { className: 'pvtOutput' },
638 _react2.default.createElement(_PivotTable2.default, (0, _immutabilityHelper2.default)(this.props, {
639 data: { $set: this.materializedInput }
640 }))
641 );
642
643 if (horizUnused) {
644 return _react2.default.createElement(
645 'table',
646 { className: 'pvtUi' },
647 _react2.default.createElement(
648 'tbody',
649 { onClick: function onClick() {
650 return _this8.setState({ openDropdown: false });
651 } },
652 _react2.default.createElement(
653 'tr',
654 null,
655 rendererCell,
656 unusedAttrsCell
657 ),
658 _react2.default.createElement(
659 'tr',
660 null,
661 aggregatorCell,
662 colAttrsCell
663 ),
664 _react2.default.createElement(
665 'tr',
666 null,
667 rowAttrsCell,
668 outputCell
669 )
670 )
671 );
672 }
673
674 return _react2.default.createElement(
675 'table',
676 { className: 'pvtUi' },
677 _react2.default.createElement(
678 'tbody',
679 { onClick: function onClick() {
680 return _this8.setState({ openDropdown: false });
681 } },
682 _react2.default.createElement(
683 'tr',
684 null,
685 rendererCell,
686 aggregatorCell,
687 colAttrsCell
688 ),
689 _react2.default.createElement(
690 'tr',
691 null,
692 unusedAttrsCell,
693 rowAttrsCell,
694 outputCell
695 )
696 )
697 );
698 }
699 }]);
700
701 return PivotTableUI;
702}(_react2.default.PureComponent);
703
704PivotTableUI.propTypes = Object.assign({}, _PivotTable2.default.propTypes, {
705 onChange: _propTypes2.default.func.isRequired,
706 hiddenAttributes: _propTypes2.default.arrayOf(_propTypes2.default.string),
707 hiddenFromAggregators: _propTypes2.default.arrayOf(_propTypes2.default.string),
708 hiddenFromDragDrop: _propTypes2.default.arrayOf(_propTypes2.default.string),
709 unusedOrientationCutoff: _propTypes2.default.number,
710 menuLimit: _propTypes2.default.number
711});
712
713PivotTableUI.defaultProps = Object.assign({}, _PivotTable2.default.defaultProps, {
714 hiddenAttributes: [],
715 hiddenFromAggregators: [],
716 hiddenFromDragDrop: [],
717 unusedOrientationCutoff: 85,
718 menuLimit: 500
719});
720
721exports.default = PivotTableUI;
722module.exports = exports['default'];
723//# sourceMappingURL=PivotTableUI.js.map
\No newline at end of file