d2-ui
Version:
428 lines (380 loc) • 17.4 kB
JavaScript
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; }; }();
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; }
import React from 'react';
import PropTypes from 'prop-types';
import LinearProgress from 'material-ui/LinearProgress';
import ModelBase from 'd2/lib/model/Model';
import ModelCollection from 'd2/lib/model/ModelCollection';
import TreeView from '../tree-view/TreeView.component';
var styles = {
progress: {
position: 'absolute',
display: 'inline-block',
width: '100%',
left: -8
},
progressBar: {
height: 2,
backgroundColor: 'transparent'
},
spacer: {
position: 'relative',
display: 'inline-block',
width: '1.2rem',
height: '1rem'
},
label: {
display: 'inline-block',
outline: 'none'
},
ouContainer: {
borderColor: 'transparent',
borderStyle: 'solid',
borderWidth: '1px',
borderRightWidth: 0,
borderRadius: '3px 0 0 3px',
background: 'transparent',
paddingLeft: 2,
outline: 'none'
},
currentOuContainer: {
background: 'rgba(0,0,0,0.05)',
borderColor: 'rgba(0,0,0,0.1)'
},
memberCount: {
fontSize: '0.75rem',
marginLeft: 4
}
};
var OrgUnitTree = function (_React$Component) {
_inherits(OrgUnitTree, _React$Component);
function OrgUnitTree(props) {
_classCallCheck(this, OrgUnitTree);
var _this = _possibleConstructorReturn(this, (OrgUnitTree.__proto__ || Object.getPrototypeOf(OrgUnitTree)).call(this, props));
_this.state = {
children: props.root.children === false || Array.isArray(props.root.children) && props.root.children.length === 0 ? [] : undefined,
loading: false
};
if (props.root.children instanceof ModelCollection && !props.root.children.hasUnloadedData) {
_this.state.children = props.root.children.toArray()
// Sort here since the API returns nested children in random order
.sort(function (a, b) {
return a.displayName.localeCompare(b.displayName);
});
}
_this.loadChildren = _this.loadChildren.bind(_this);
_this.handleSelectClick = _this.handleSelectClick.bind(_this);
return _this;
}
_createClass(OrgUnitTree, [{
key: 'componentDidMount',
value: function componentDidMount() {
var _this2 = this;
if (this.props.initiallyExpanded.some(function (ou) {
return ou.includes('/' + _this2.props.root.id);
})) {
this.loadChildren();
}
}
}, {
key: 'componentWillReceiveProps',
value: function componentWillReceiveProps(newProps) {
if (newProps.initiallyExpanded.some(function (ou) {
return ou.includes('/' + newProps.root.id);
}) || newProps.idsThatShouldBeReloaded.includes(newProps.root.id)) {
this.loadChildren();
}
}
}, {
key: 'setChildState',
value: function setChildState(children) {
if (this.props.onChildrenLoaded) {
this.props.onChildrenLoaded(children);
}
this.setState({
children: children.toArray().sort(function (a, b) {
return a.displayName.localeCompare(b.displayName);
}),
loading: false
});
}
}, {
key: 'loadChildren',
value: function loadChildren() {
var _this3 = this;
if (this.state.children === undefined && !this.state.loading || this.props.idsThatShouldBeReloaded.indexOf(this.props.root.id) >= 0) {
this.setState({ loading: true });
var root = this.props.root;
// d2.ModelCollectionProperty.load takes a second parameter `forceReload` and will just return
// the current valueMap unless either `this.hasUnloadedData` or `forceReload` are true
root.children.load({ fields: 'id,displayName,children::isNotEmpty,path,parent' }, this.props.forceReloadChildren).then(function (children) {
_this3.setChildState(children);
});
}
}
}, {
key: 'handleSelectClick',
value: function handleSelectClick(e) {
if (this.props.onSelectClick) {
this.props.onSelectClick(e, this.props.root);
}
e.stopPropagation();
}
}, {
key: 'shouldIncludeOrgUnit',
value: function shouldIncludeOrgUnit(orgUnit) {
if (!this.props.orgUnitsPathsToInclude || this.props.orgUnitsPathsToInclude.length === 0) {
return true;
}
return !!this.props.orgUnitsPathsToInclude.some(function (ou) {
return ou.includes('/' + orgUnit.id);
});
}
}, {
key: 'renderChild',
value: function renderChild(orgUnit, expandedProp) {
if (this.shouldIncludeOrgUnit(orgUnit)) {
return React.createElement(OrgUnitTree, {
key: orgUnit.id,
root: orgUnit,
selected: this.props.selected,
initiallyExpanded: expandedProp,
onSelectClick: this.props.onSelectClick,
currentRoot: this.props.currentRoot,
onChangeCurrentRoot: this.props.onChangeCurrentRoot,
labelStyle: this.props.labelStyle,
selectedLabelStyle: this.props.selectedLabelStyle,
arrowSymbol: this.props.arrowSymbol,
idsThatShouldBeReloaded: this.props.idsThatShouldBeReloaded,
hideCheckboxes: this.props.hideCheckboxes,
onChildrenLoaded: this.props.onChildrenLoaded,
hideMemberCount: this.props.hideMemberCount,
orgUnitsPathsToInclude: this.props.orgUnitsPathsToInclude,
forceReloadChildren: this.props.forceReloadChildren
});
}
return null;
}
}, {
key: 'renderChildren',
value: function renderChildren() {
var _this4 = this;
// If initiallyExpanded is an array, remove the current root id and pass the rest on
// If it's a string, pass it on unless it's the current root id
var expandedProp = Array.isArray(this.props.initiallyExpanded) ? this.props.initiallyExpanded.filter(function (id) {
return id !== _this4.props.root.id;
}) : this.props.initiallyExpanded !== this.props.root.id && this.props.initiallyExpanded || [];
if (Array.isArray(this.state.children) && this.state.children.length > 0) {
return this.state.children.map(function (orgUnit) {
return _this4.renderChild(orgUnit, expandedProp);
});
}
if (this.state.loading) {
return React.createElement(
'div',
{ style: styles.progress },
React.createElement(LinearProgress, { style: styles.progressBar })
);
}
return null;
}
}, {
key: 'render',
value: function render() {
var _this5 = this;
var currentOu = this.props.root;
// True if this OU has children = is not a leaf node
var hasChildren = this.state.children === undefined || Array.isArray(this.state.children) && this.state.children.length > 0;
// True if a click handler exists
var isSelectable = !!this.props.onSelectClick;
var pathRegEx = new RegExp('/' + currentOu.id + '$');
var memberRegEx = new RegExp('/' + currentOu.id);
var isSelected = this.props.selected && this.props.selected.some(function (ou) {
return pathRegEx.test(ou);
});
// True if this OU is the current root
var isCurrentRoot = this.props.currentRoot && this.props.currentRoot.id === currentOu.id;
// True if this OU should be expanded by default
var isInitiallyExpanded = this.props.initiallyExpanded.some(function (ou) {
return ou.includes('/' + currentOu.id);
});
// True if this OU can BECOME the current root, which means that:
// 1) there is a change root handler
// 2) this OU is not already the current root
// 3) this OU has children (is not a leaf node)
var canBecomeCurrentRoot = this.props.onChangeCurrentRoot && !isCurrentRoot && hasChildren;
var memberCount = this.props.selected !== undefined ? this.props.selected.filter(function (ou) {
return memberRegEx.test(ou);
}).length : currentOu.memberCount;
// Hard coded styles for OU name labels - can be overridden with the selectedLabelStyle and labelStyle props
var labelStyle = Object.assign({}, styles.label, {
fontWeight: isSelected ? 500 : 300,
color: isSelected ? 'orange' : 'inherit',
cursor: canBecomeCurrentRoot ? 'pointer' : 'default'
}, isSelected ? this.props.selectedLabelStyle : this.props.labelStyle);
// Styles for this OU and OUs contained within it
var ouContainerStyle = Object.assign({}, styles.ouContainer, isCurrentRoot ? styles.currentOuContainer : {});
// Wrap the change root click handler in order to stop event propagation
var setCurrentRoot = function setCurrentRoot(e) {
e.stopPropagation();
_this5.props.onChangeCurrentRoot(currentOu);
};
var label = React.createElement(
'div',
{
style: labelStyle,
onClick: canBecomeCurrentRoot && setCurrentRoot || isSelectable && this.handleSelectClick,
role: 'button',
tabIndex: 0
},
isSelectable && !this.props.hideCheckboxes && React.createElement('input', {
type: 'checkbox',
readOnly: true,
disabled: !isSelectable,
checked: isSelected,
onClick: this.handleSelectClick
}),
currentOu.displayName,
hasChildren && !this.props.hideMemberCount && !!memberCount && React.createElement(
'span',
{ style: styles.memberCount },
'(',
memberCount,
')'
)
);
if (hasChildren) {
return React.createElement(
TreeView,
{
label: label,
onExpand: this.loadChildren,
persistent: true,
initiallyExpanded: isInitiallyExpanded,
arrowSymbol: this.props.arrowSymbol,
className: 'orgunit with-children',
style: ouContainerStyle
},
this.renderChildren()
);
}
return React.createElement(
'div',
{
onClick: isSelectable && this.handleSelectClick,
className: 'orgunit without-children',
style: ouContainerStyle,
role: 'button',
tabIndex: 0
},
React.createElement('div', { style: styles.spacer }),
label
);
}
}]);
return OrgUnitTree;
}(React.Component);
function orgUnitPathPropValidator(propValue, key, compName, location, propFullName) {
if (!/(\/[a-zA-Z][a-zA-Z0-9]{10})+/.test(propValue[key])) {
return new Error('Invalid org unit path `' + propValue[key] + '` supplied to `' + compName + '.' + propFullName + '`');
}
return undefined;
}
OrgUnitTree.propTypes = {
/**
* The root OrganisationUnit of the tree
*
* If the root OU is known to have no children, the `children` property of the root OU should be either
* `false` or an empty array. If the children property is undefined, the children will be fetched from
* the server when the tree is expanded.
*/
root: PropTypes.instanceOf(ModelBase).isRequired,
/**
* An array of paths of selected OUs
*
* The path of an OU is the UIDs of the OU and all its parent OUs separated by slashes (/)
*/
selected: PropTypes.arrayOf(orgUnitPathPropValidator),
/**
* An array of OU paths that will be expanded automatically as soon as they are encountered
*
* The path of an OU is the UIDs of the OU and all its parent OUs separated by slashes (/)
*/
initiallyExpanded: PropTypes.arrayOf(orgUnitPathPropValidator),
/**
* onSelectClick callback, which is triggered when a click triggers the selection of an organisation unit
*
* The onSelectClick callback will receive two arguments: The original click event, and the OU that was clicked
*/
onSelectClick: PropTypes.func,
/**
* onChangeCurrentRoot callback, which is triggered when the change current root label is clicked. Setting this also
* enables the display of the change current root label
*
* the onChangeCurrentRoot callback will receive two arguments: The original click event, and the organisation unit
* model object that was selected as the new root
*/
onChangeCurrentRoot: PropTypes.func,
/**
* Organisation unit model representing the current root
*/
currentRoot: PropTypes.object,
/**
* onChildrenLoaded callback, which is triggered when the children of this root org unit have been loaded
*
* The callback receives one argument: A D2 ModelCollection object that contains all the newly loaded org units
*/
onChildrenLoaded: PropTypes.func,
/**
* Custom styling for OU labels
*/
labelStyle: PropTypes.object,
/**
* Custom styling for the labels of selected OUs
*/
selectedLabelStyle: PropTypes.object,
/**
* An array of organisation unit IDs that should be reloaded from the API
*/
idsThatShouldBeReloaded: PropTypes.arrayOf(PropTypes.string),
/**
* Custom arrow symbol
*/
arrowSymbol: PropTypes.string,
/**
* If true, don't display checkboxes next to org unit labels
*/
hideCheckboxes: PropTypes.bool,
/**
* if true, don't display the selected member count next to org unit labels
*/
hideMemberCount: PropTypes.bool,
/**
* Array of paths of Organisation Units to include on tree. If not defined or empty, all children from root to leafs will be shown
*/
orgUnitsPathsToInclude: PropTypes.array,
/**
* If true `root.children.load` (a method on d2.ModelCollectionProperty) will be called with forceReload set to true, which is required
* for dynamic OrgUnitTrees, i.e. in cases where parent-child relations are updated
*/
forceReloadChildren: PropTypes.bool
};
OrgUnitTree.defaultProps = {
selected: [],
initiallyExpanded: [],
onSelectClick: undefined,
onChangeCurrentRoot: undefined,
currentRoot: undefined,
onChildrenLoaded: undefined,
labelStyle: {},
selectedLabelStyle: {},
idsThatShouldBeReloaded: [],
arrowSymbol: undefined,
hideCheckboxes: false,
hideMemberCount: false,
orgUnitsPathsToInclude: null,
forceReloadChildren: false
};
export default OrgUnitTree;