hbp-quickfire
Version:
A library of useful user-interface components built with React on top of React Bootstrap and MobX
261 lines (195 loc) • 21.3 kB
JavaScript
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 _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 _class, _temp2, _dec, _class2;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;} /*
* Copyright (c) Human Brain Project
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import React from "react";
import injectStyles from "react-jss";import FormControl from "react-bootstrap/lib/FormControl";import FormGroup from "react-bootstrap/lib/FormGroup";import Glyphicon from "react-bootstrap/lib/Glyphicon";import InputGroup from "react-bootstrap/lib/InputGroup";import Button from "react-bootstrap/lib/Button";import Label from "react-bootstrap/lib/Label";import intersection from "lodash/intersection";import find from "lodash/find";import isString from "lodash/isString";var
Tree = (_temp2 = _class = function (_React$Component) {_inherits(Tree, _React$Component);function Tree() {var _ref;var _temp, _this, _ret;_classCallCheck(this, Tree);for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {args[_key] = arguments[_key];}return _ret = (_temp = (_this = _possibleConstructorReturn(this, (_ref = Tree.__proto__ || Object.getPrototypeOf(Tree)).call.apply(_ref, [this].concat(args))), _this), _this.
state = { query: null, queryInput: "" }, _this.
timer = null, _temp), _possibleConstructorReturn(_this, _ret);} //Timer identifier bound to the triggering of the search into the tree structure
//Gets reset if a key is stroke by user before the end of the timer
//Is triggered if no keys are stroke before the end of the timer
//Limits the number of time the search is done on the entire tree structure
_createClass(Tree, [{ key: "render", value: function render() {var defaultExpanded = this.props.defaultExpanded;if (!defaultExpanded || defaultExpanded.length === undefined) {
defaultExpanded = [];
} else if (defaultExpanded.length > 0 && isString(defaultExpanded[0])) {
defaultExpanded = [defaultExpanded];
}
return (
React.createElement("div", { className: "quickfire-tree" },
React.createElement(FormGroup, { controlId: "Tree Node Search", className: "quickfire-tree-search" },
React.createElement(InputGroup, null,
React.createElement(InputGroup.Addon, null,
React.createElement(Glyphicon, { glyph: "search" })),
React.createElement(FormControl, { type: "text",
className: "quickfire-tree-search-input",
placeholder: "Search",
onChange: this.handleChange.bind(this),
value: this.state.queryInput }),
React.createElement(InputGroup.Button, null,
React.createElement(Button, { onClick: this.reset.bind(this) }, React.createElement(Glyphicon, { glyph: "remove" }))))),
React.createElement(TreeNode, {
data: this.props.data,
onSelect: this.props.onSelect,
query: this.state.query,
mappingLabel: this.props.mappingLabel,
mappingChildren: this.props.mappingChildren,
selectOnlyLeaf: this.props.selectOnlyLeaf,
selectedNodes: this.props.selectedNodes,
expandToSelectedNodes: this.props.expandToSelectedNodes,
defaultExpanded: defaultExpanded,
showOnlySearchedNodes: this.props.showOnlySearchedNodes,
level: 0 })));
} }, { key: "handleChange", value: function handleChange(
e) {
e.stopPropagation();
var self = this;
var newQuery = e.target.value;
self.setState({ queryInput: e.target.value });
if (this.timer) {
clearTimeout(this.timer);
}
var newQueryRegexp = void 0;
try {
//We use a regexp to search into the serialized tree structure as it's fast and practical to be able to use a regexp for search
newQueryRegexp = newQuery.length >= 1 ? new RegExp(":\".*?" + newQuery + ".*?\"", "i") : null;
this.timer = setTimeout(function () {
self.setState({ query: newQueryRegexp });
}, 750);
}
catch (e) {"";} //Silent fail is intended to prevent errors thrown during the type of a regexp by the user
} }, { key: "reset", value: function reset()
{
this.setState({ query: null, queryInput: "" });
} }]);return Tree;}(React.Component), _class.defaultProps = { mappingLabel: "label", mappingChildren: "children", selectOnlyLeaf: false, selectedNodes: [], expandToSelectedNodes: false, defaultExpanded: [] }, _temp2);export { Tree as default };
var treeNodeStyles = {
node: {
listStyleType: "none",
"& .quickfire-tree-node-selector": {
marginLeft: "3px" },
"& .quickfire-tree-node-expand:focus": {
outline: "none" },
"&.hidden": {
display: "none" } },
childrenList: {
marginLeft: "20px",
paddingLeft: 0 },
leafPlaceHolder: {
width: "23px",
display: "inline-block" },
labelSelected: {
display: "inline-block",
marginRight: "4px",
padding: "2px 3px 3px 2px",
fontSize: "0.6em",
verticalAlign: "middle" } };var
TreeNode = (_dec = injectStyles(treeNodeStyles), _dec(_class2 = function (_React$Component2) {_inherits(TreeNode, _React$Component2);
function TreeNode(props) {_classCallCheck(this, TreeNode);var _this2 = _possibleConstructorReturn(this, (TreeNode.__proto__ || Object.getPrototypeOf(TreeNode)).call(this,
props));
_this2.matchesExpanded = [];
if (props.expandToSelectedNodes && _this2.hasSelectedChildren(props.data)) {
_this2.state = { expand: true };
} else if (props.query && props.data.children) {
_this2.state = { expand: props.query.test(JSON.stringify(props.data.children)) };
} else if (_this2.matchDefaultExpanded()) {
_this2.state = { expand: true };
} else {
_this2.state = { expand: false };
}return _this2;
}_createClass(TreeNode, [{ key: "matchDefaultExpanded", value: function matchDefaultExpanded()
{var _this3 = this;
this.matchesExpanded = [];
if (this.props.defaultExpanded && this.props.defaultExpanded.length > 0) {
this.props.defaultExpanded.forEach(function (path) {
if (path.length > _this3.props.level && new RegExp(path[_this3.props.level], "gi").test(_this3.props.data[_this3.props.mappingLabel])) {
_this3.matchesExpanded.push(path);
}
});
}
return this.matchesExpanded.length > 0;
} }, { key: "hasSelectedChildren", value: function hasSelectedChildren(
node) {var _this4 = this;
return node.children !== undefined && node.children.length !== 0 && (
intersection(this.props.selectedNodes, node.children).length > 0 ||
find(node.children, function (child) {return _this4.hasSelectedChildren(child);}));
} }, { key: "render", value: function render()
{var _this5 = this;var _props = _extends({},
this.props),classes = _props.classes,data = _props.data,mappingLabel = _props.mappingLabel,mappingChildren = _props.mappingChildren,selectOnlyLeaf = _props.selectOnlyLeaf,selectedNodes = _props.selectedNodes,expandToSelectedNodes = _props.expandToSelectedNodes,showOnlySearchedNodes = _props.showOnlySearchedNodes;
var hideThisNode = false;
//showOnlySearchedNodes: If search doesnt match the node and we want to hide parts of the tree that are not relevant to the search
if (showOnlySearchedNodes && this.props.query) {
var dataCopy = JSON.parse(JSON.stringify(data));
if (dataCopy[mappingChildren]) {
delete dataCopy[mappingChildren];
}
var match = this.props.query.test(JSON.stringify(dataCopy));
if (!match && !this.state.expand) {
hideThisNode = true;
} else if (match && this.state.expand) {
showOnlySearchedNodes = false;
}
}
var label = data[mappingLabel];
var children = undefined;
var isSelected = this.props.selectedNodes.indexOf(this.props.data) !== -1;
if (data[mappingChildren] && data[mappingChildren].length !== undefined) {
if (this.state.expand) {
children = data[mappingChildren].map(function (child, index) {
return (
React.createElement(TreeNode, {
data: child,
onSelect: _this5.props.onSelect,
key: index,
query: _this5.props.query,
classes: _this5.props.classes,
mappingLabel: mappingLabel,
mappingChildren: mappingChildren,
selectOnlyLeaf: selectOnlyLeaf,
selectedNodes: selectedNodes,
expandToSelectedNodes: expandToSelectedNodes,
defaultExpanded: _this5.props.defaultExpanded,
showOnlySearchedNodes: showOnlySearchedNodes,
level: _this5.props.level + 1 }));
});
} else {
children = [];
}
}
return (
React.createElement("li", { className: classes.node + " " + (hideThisNode && "hidden") + " quickfire-tree-node" },
children != undefined ?
React.createElement(Button, { className: "quickfire-tree-node-expand", bsStyle: "link", bsSize: "xsmall", onClick: this.toggle.bind(this) },
React.createElement(Glyphicon, { glyph: this.state.expand ? "minus" : "plus" })) :
React.createElement("span", { className: classes.leafPlaceHolder }),
React.createElement("a", { className: "quickfire-tree-node-selector", onClick: this.handleClickSelect.bind(this), style: { cursor: "pointer", textDecoration: "none", color: "inherit" } },
isSelected ?
React.createElement(Label, { bsStyle: "success", className: classes.labelSelected }, React.createElement(Glyphicon, { glyph: "ok" })) :
null,
label),
children != undefined && this.state.expand &&
React.createElement("ul", { className: classes.childrenList + " quickfire-tree-node-children" }, children)));
} }, { key: "componentWillReceiveProps", value: function componentWillReceiveProps(
nextProps) {
if (nextProps.query !== this.props.query) {
if (nextProps.query && nextProps.data.children) {
this.setState({ expand: nextProps.query.test(JSON.stringify(nextProps.data.children)) });
} else if (this.matchDefaultExpanded()) {
this.setState({ expand: true });
} else {
this.setState({ expand: false });
}
}
} }, { key: "handleClickSelect", value: function handleClickSelect()
{var _props2 = _extends({},
this.props),mappingChildren = _props2.mappingChildren;
if (this.props.selectOnlyLeaf && this.props.data[mappingChildren] !== undefined && this.props.data[mappingChildren].length > 0) {
this.toggle();
} else if (this.props.onSelect) {
this.props.onSelect(this.props.data);
}
} }, { key: "toggle", value: function toggle()
{
this.setState({ expand: !this.state.expand });
} }]);return TreeNode;}(React.Component)) || _class2);