UNPKG

nest-parrot

Version:
858 lines (850 loc) 25.6 kB
(function(window, $, React, ReactDOM, $pt) { var NTree = React.createClass($pt.defineCellComponent({ displayName: 'NTree', statics: { ROOT_LABEL: 'Root', FOLDER_ICON: 'folder-o', FOLDER_OPEN_ICON: 'folder-open-o', FILE_ICON: 'file-o', OP_FOLDER_LEAF_ICON: 'angle-right', OP_FOLDER_ICON: 'angle-double-right', OP_FOLDER_OPEN_ICON: 'angle-double-down', OP_FILE_ICON: '', NODE_SEPARATOR : ';', ROOT_ID : '0', FILTER_PLACEHOLDER: 'Search...', convertValueTreeToArray: function(nodeValues, id) { var array = []; var push = function(node, id) { Object.keys(node).forEach(function(key) { if (key == 'selected' && node[key]) { if (id != NTree.ROOT_ID) { array.push(id); } else { array.selected = node.selected ? true : undefined; } } else { push(node[key], key); } }); }; push(nodeValues, id); return array; } }, getDefaultProps: function() { return { defaultOptions: { root: true, check: false, inactiveSlibing: true, opIconEnabled: false, multiple: true, valueAsArray: false, hierarchyCheck: false, expandLevel: 1, border: false, expandButton: { comp: { icon: 'plus-square-o', style: 'link' } }, collapseButton: { comp: { icon: 'minus-square-o', style: 'link' } } } }; }, getInitialState: function() { var expandBtn = $.extend(true, {}, this.getComponentOption('expandButton')); if (expandBtn) { if (!expandBtn.comp.click) { expandBtn.comp.click = this.expandAll; } expandBtn = $pt.createCellLayout('expand', expandBtn); } var collapseBtn = $.extend(true, {}, this.getComponentOption('collapseButton')); if (collapseBtn) { if (!collapseBtn.comp.click) { collapseBtn.comp.click = this.collapseAll; } collapseBtn = $pt.createCellLayout('collapse', collapseBtn); } return { activeNodes: {}, root: {text: this.getRootLabel(), id: NTree.ROOT_ID}, expandButton: expandBtn, collapseButton: collapseBtn, initialized: false }; }, componentWillMount: function() { if(this.state.root.children) { this.setState({initialized: true}); } }, componentWillUpdate: function() { if(!this.state.initialized && this.state.root.children && this.isSearchable()) { var nodes = this.filterNodes(this.state.root); this.setState({root: nodes, initialized: true}); } }, renderCheck: function(node, nodeId) { var canSelected = this.isNodeCanSelect(node); if (!canSelected) { return null; } var modelValue = this.getValueFromModel(); modelValue = modelValue ? modelValue : {}; var model = $pt.createModel({selected: this.isNodeChecked(nodeId)}); model.useBaseAsCurrent(); var layoutJSON = { comp: { type: $pt.ComponentConstants.Check } }; var valueCanChange = this.isNodeCheckCanChange(node); if (valueCanChange != null) { layoutJSON.comp.enabled = valueCanChange; } var layout = $pt.createCellLayout('selected', layoutJSON); model.addPostChangeListener('selected', this.onNodeCheckChanged.bind(this, node, nodeId)); return <$pt.Components.NCheck model={model} layout={layout} view={this.isViewMode()}/>; }, renderNode: function(parentNodeId, node) { if(node.visible === false) { return null; } var nodeId = this.getNodeId(parentNodeId, node); var opIcon = null; if (this.getComponentOption('opIconEnabled')) { var expandableIconAttrs = { iconClassName: 'node-op-icon', fixWidth: true, icon: this.getNodeOperationIcon(node, nodeId) }; opIcon = (<a href='javascript:void(0);' onClick={this.onNodeClicked.bind(this, node, nodeId)}> <$pt.Components.NIcon {...expandableIconAttrs} /> </a>); } var folderIconAttrs = { icon: this.getNodeIcon(node, nodeId), fixWidth: true, iconClassName: 'node-icon' }; var folderIcon = (<a href='javascript:void(0);' onClick={this.onNodeClicked.bind(this, node, nodeId)}> <$pt.Components.NIcon {...folderIconAttrs}/> </a>); var _this = this; var buttons = this.getComponentOption('nodeOperations'); buttons = buttons ? (Array.isArray(buttons) ? buttons : [buttons]) : []; buttons = buttons.map(function(button, buttonIndex) { var visible = true; if (typeof button.visible === 'boolean') { visible = button.visible; } else if (typeof button.visible === 'function') { visible = button.visible.call(_this, node); } if (!visible) { return null; } var icon = { icon: button.icon, fixWidth: true, }; return (<a href='javascript:void(0);' onClick={_this.onNodeOperationClicked.bind(_this, node, button.click)} className='node-button' key={buttonIndex} title={button.text}> <$pt.Components.NIcon {...icon}/> </a>) }).filter(function(button) { return button != null; }); var active = this.isActive(nodeId) ? 'active' : null; return ( <li className={active} key={nodeId} data-node-id={nodeId}> <div className='node-content'> {opIcon} {folderIcon} {this.renderCheck(node, nodeId)} <a className={'node-text-link-' + buttons.length} href='javascript:void(0);' onClick={this.onNodeLabelClicked.bind(this, node, nodeId)}> <span className='node-text'>{this.getNodeText(node)}</span> </a> {buttons} </div> {this.getComponentOption('lazyLoad') && !active ? null : this.renderNodes(node, nodeId)} </li> ); }, renderNodes: function(parent, parentNodeId) { var children = parent.children; if (children && children.length > 0 && children.some(function(item) { return item.visible !== false; })) { return ( <ul className='nav'> {children.map(this.renderNode.bind(this, parentNodeId))} </ul> ); } else { return null; } }, renderRoot: function() { return (<ul className='nav'> {this.renderNode(null, this.state.root)} </ul>); }, renderTopLevel: function() { return (<$pt.Components.NCodeTableWrapper codetable={this.getCodeTable()} renderer={this.getRealTopLevelRenderer} model={this.getModel()} layout={this.getLayout()} onMounted={this.initExpand}/>); }, getRealTopLevelRenderer: function() { var root = this.state.root; root.children = this.getTopLevelNodes(); return this.isRootPaint() ? this.renderRoot() : this.renderNodes(root, this.getNodeId(null, root)); }, renderButtons: function() { var expand = this.state.expandButton ? <$pt.Components.NFormButton model={this.getModel()} layout={this.state.expandButton}/> : null; var collapse = this.state.collapseButton ? <$pt.Components.NFormButton model={this.getModel()} layout={this.state.collapseButton}/> : null; if (expand || collapse) { return (<span className='buttons'> {expand}{collapse} </span>); } else { return null; } }, renderFilterText: function() { var model = $pt.createModel({ text: this.state.filterText, // on mobile phone, set as true to disable the soft keyboard // set as false to enable it when popover render completed // see #onPopoverRenderComplete disabled: this.isMobilePhone() ? true : false }); var layout = $pt.createCellLayout('text', { comp: { placeholder: NTree.FILTER_PLACEHOLDER, /* rightAddon: { icon: 'search', click: this.onFilterTextChange }, */ enabled: { depends: 'disabled', when: function(model) { return model.get('disabled') !== true; } } }, css: {comp: 'bottom-margin-5'}, evt: { keyUp: this.onSearchEnterKeyUp } }); model.addPostChangeListener('text', this.onFilterTextChange); this.state.filterModel = model; return <$pt.Components.NText model={model} layout={layout} />; }, initExpand: function() { var expandLevel = this.getComponentOption('expandLevel'); if (expandLevel == null) { // default expand root expandLevel = 0; } if (expandLevel === 'all') { expandLevel = 9999; } if (this.state.root.children) { // this.state.root.children = this.getTopLevelNodes(); this.expandTo(expandLevel); } }, render: function() { var styles = {}; if (this.getComponentOption('height')) { styles.height = this.getComponentOption('height'); } if (this.getComponentOption('maxHeight')) { styles.maxHeight = this.getComponentOption('maxHeight'); } var css = this.getComponentCSS('n-tree'), searchable = this.isSearchable(); if (searchable) { css += ' searchable'; } if (this.getComponentOption('border')) { css += ' border'; } return ( <div className={css} style={styles} ref='me'> {searchable ? this.renderFilterText() : null} {this.renderTopLevel()} {this.renderButtons()} </div> ); }, onSearchEnterKeyUp: function (evt) { evt.preventDefault(); evt.stopPropagation(); if (evt.keyCode !== 13) { return; } this.onFilterTextChange(evt); }, onFilterTextChange: function(evt) { var val = evt.new || this.state.filterModel.get('text'); var filteredNodes = this.filterNodes(this.state.root, val); this.setState({ filterText: val, root: filteredNodes }); }, filterNodes: function(node, value) { if(node.children && node.children.length > 0) { node.children = node.children.map(function(item) { return this.filterNodes(item, value); }.bind(this)); } // console.log('[filterNodes]', node, value) if(!value || node.text.toUpperCase().indexOf(value.toUpperCase()) >= 0 || (node.children && node.children.some(function(item) { return item.visible !== false; }))) { node.visible = true; } else { node.visible = false; } return node; }, onNodeClicked: function(node, nodeId) { if (!this.isLeaf(node)) { if (this.state.activeNodes[nodeId]) { this.collapseNode(node, nodeId); } else { this.expandNode(node, nodeId); } } }, onNodeLabelClicked: function(node, nodeId, evt) { var nodeClick = this.getComponentOption('nodeClick'); if (nodeClick) { var target = $(evt.target); var callback = function(keepOtherHighlights) { if (!this.isMultipleHighlight() || !keepOtherHighlights) { $(ReactDOM.findDOMNode(this.refs.me)).find('div.node-content').removeClass('node-highlight'); } target.closest('div.node-content').addClass('node-highlight'); }.bind(this); var returnValue = nodeClick.call(this, node, callback, evt); if (returnValue === true) { callback.call(this); } else if (returnValue === 'keep') { callback.call(this, true); } else if (returnValue && returnValue.done) { returnValue.done(function(data) { callback.call(this, data); }.bind(this)); } } else { this.onNodeClicked(node, nodeId); } }, onNodeOperationClicked: function(node, click) { if (click) { click.call(this, node); } }, onNodeCheckChanged: function(node, nodeId, evt, toChildOnly) { var hierarchyCheck = this.isHierarchyCheck(); var modelValue = this.getValueFromModel(); if (this.isValueAsArray()) { modelValue = modelValue ? modelValue : []; if (hierarchyCheck) { this.checkNodeHierarchy(node, nodeId, evt.new, modelValue); this.hierarchyCheckToAncestors(nodeId, modelValue); } else { this.checkNode(nodeId, evt.new, modelValue); } if (this.getValueFromModel() != modelValue) { // simply set to model this.setValueToModel(modelValue); } else { // fire event manually this.getModel().firePostChangeEvent(this.getDataId(), modelValue, modelValue); } } else { if (!modelValue) { modelValue = {}; } if (hierarchyCheck) { this.checkNodeHierarchy(node, nodeId, evt.new, modelValue); this.hierarchyCheckToAncestors(nodeId, modelValue); } else { this.checkNode(nodeId, evt.new, modelValue); } if (this.getValueFromModel() != modelValue) { // simply set to model this.setValueToModel(modelValue); } else { // fire event manually this.getModel().firePostChangeEvent(this.getDataId(), modelValue, modelValue); } } }, isValueAsArray: function() { return this.getComponentOption('valueAsArray'); }, /** * check or uncheck node. will not fire post change event. */ checkNode: function(nodeId, value, modelValue) { if (this.isValueAsArray()) { if (!this.isMultipleSelection()) { // no multiple selection modelValue.length = 0; } if (nodeId == this.state.root.id) { modelValue.selected = value; } else { var ids = nodeId.split(NTree.NODE_SEPARATOR); var id = ids[ids.length - 1]; var index = modelValue.findIndex(function(value) { return value == id; }); if (value && index == -1) { modelValue.push(id); } else if (!value && index != -1) { modelValue.splice(index, 1); } } } else { if (!this.isMultipleSelection()) { // no multiple selection Object.keys(modelValue).forEach(function(key) { delete modelValue[key]; }); } if (nodeId == this.state.root.id) { $pt.setValueIntoJSON(modelValue, 'selected', value); } else { var segments = nodeId.split(NTree.NODE_SEPARATOR); $pt.setValueIntoJSON(modelValue, segments.slice(1).join($pt.PROPERTY_SEPARATOR) + $pt.PROPERTY_SEPARATOR + 'selected', value); } } return modelValue; }, /** * check or uncheck node hierarchy. will not fire post change event. */ checkNodeHierarchy: function(node, nodeId, value, modelValue) { modelValue = this.checkNode(nodeId, value, modelValue); if (node.children) { var _this = this; node.children.forEach(function(child) { var childId = _this.getNodeId(nodeId, child); _this.checkNodeHierarchy(child, childId, value, modelValue); }); } return modelValue; }, isNodeChecked: function(nodeId, modelValue) { modelValue = modelValue ? modelValue : this.getValueFromModel(); modelValue = modelValue ? modelValue : {}; if (Array.isArray(modelValue)) { if (nodeId == this.state.root.id) { return modelValue.selected; } else { var ids = nodeId.split(NTree.NODE_SEPARATOR); var id = ids[ids.length - 1]; return -1 != modelValue.findIndex(function(value) { return value == id; }); } } else { if (nodeId == this.state.root.id) { return $pt.getValueFromJSON(modelValue, 'selected'); } else { return $pt.getValueFromJSON(modelValue, nodeId.split(NTree.NODE_SEPARATOR).slice(1).join($pt.PROPERTY_SEPARATOR) + $pt.PROPERTY_SEPARATOR + 'selected'); } } }, hierarchyCheckToAncestors: function(nodeId, modelValue) { var _this = this; var checkNodeOnChildren = function(node, nodeId) { if (node.children) { var hasUncheckedChild = false; node.children.forEach(function(child) { var checked = checkNodeOnChildren(child, _this.getNodeId(nodeId, child)); if (!checked) { hasUncheckedChild = true; } }); // window.console.log(nodeId); _this.checkNode(nodeId, !hasUncheckedChild, modelValue); return !hasUncheckedChild; } else { // no children, return checked of myself // window.console.log(nodeId); return _this.isNodeChecked(nodeId, modelValue); } }; checkNodeOnChildren(this.state.root, this.getNodeId(null, this.state.root)); // window.console.log(modelValue); }, expandTo: function(expandLevel) { var activeNodes = $.extend({}, this.state.activeNodes); var _this = this; var expand = function(parentId, node, level) { if (level < expandLevel) { var nodeId = _this.getNodeId(parentId, node); activeNodes[nodeId] = node; if (node.children) { node.children.forEach(function(child) { expand(nodeId, child, level + 1); }); } } }; expand(null, this.state.root, 0); var previousActiveNodes = null; this.setState(function(previousState, currentProps) { previousActiveNodes = previousState.activeNodes; return {activeNodes: activeNodes}; }, function() { _this.notifyEvent({ type: 'expand', before: previousActiveNodes, after: activeNodes }); }); }, expandAll: function() { var activeNodes = $.extend({}, this.state.activeNodes); var root = this.state.root; var expand = function(node, parentNodeId) { if (!this.isLeaf(node)) { var nodeId = this.getNodeId(parentNodeId, node); activeNodes[nodeId] = node; var _this = this; node.children.forEach(function(child) { expand.call(_this, child, nodeId); }); } }; expand.call(this, root, null); // this.setState({activeNodes: activeNodes}); var _this = this; var previousActiveNodes = null; this.setState(function(previousState, currentProps) { previousActiveNodes = previousState.activeNodes; return {activeNodes: activeNodes}; }, function() { _this.notifyEvent({ type: 'expand', before: previousActiveNodes, after: activeNodes }); }); }, collapseAll: function() { var _this = this; // var root = this.state.root; // var nodeIds = null; // if (this.isRootPaint()) { // // this.collapseNode(root, this.getNodeId(null, root)); // nodeIds = [this.getNodeId(null, root)]; // } else { // var rootNodeId = this.getNodeId(null, root); // if (root.children) { // // root.children.forEach(function(node) { // // this.collapseNode(node, this.getNodeId(rootNodeId, node)); // // }.bind(this)); // nodeIds = root.children.map(function(node) { // return _this.getNodeId(rootNodeId, node); // }); // } // } // var regexp = new RegExp(nodeIds.map(function(nodeId) { // return '(' + nodeId + ')'; // }).join(NTree.NODE_SEPARATOR)); // var activeNodes = $.extend({}, this.state.activeNodes); var activeNodes = {}; Object.keys(activeNodes).forEach(function(key) { if (key.match(regexp)) { delete activeNodes[key]; } }); var previousActiveNodes = null; this.setState(function(previousState, currentProps) { previousActiveNodes = previousState.activeNodes; return {activeNodes: activeNodes}; }, function() { _this.notifyEvent({ type: 'collapse', before: previousActiveNodes, after: activeNodes }); }); }, isRootPaint: function() { return this.getComponentOption('root'); }, getRootLabel: function() { var root = this.getComponentOption('root'); if (typeof root === 'string') { return root; } else { return NTree.ROOT_LABEL; } }, isSearchable: function() { return this.getComponentOption('searchable') && !this.isViewMode(); }, isActive: function(nodeId) { return this.state.activeNodes[nodeId]; }, isLeaf: function(node) { return !node.children || node.children.length == 0; }, isInactiveSlibingWhenActive: function() { return this.getComponentOption('inactiveSlibing'); }, collapseNode: function(node, nodeId) { var regexp = new RegExp(nodeId); var activeNodes = $.extend({}, this.state.activeNodes); Object.keys(activeNodes).forEach(function(key) { if (key.match(regexp)) { delete activeNodes[key]; } }); // this.setState({activeNodes: activeNodes}); var _this = this; var previousActiveNodes = null; this.setState(function(previousState, currentProps) { previousActiveNodes = previousState.activeNodes; return {activeNodes: activeNodes}; }, function() { _this.notifyEvent({ type: 'collapse', before: previousActiveNodes, after: activeNodes }); }); }, expandNode: function(node, nodeId) { var activeNodes = $.extend({}, this.state.activeNodes); if (this.isInactiveSlibingWhenActive() && !this.isLeaf(node)) { // remove all slibings and their children from active list var lastHyphen = nodeId.lastIndexOf(NTree.NODE_SEPARATOR); if (lastHyphen > 0) { var regexp = new RegExp(nodeId.substring(0, lastHyphen + 1)); Object.keys(activeNodes).forEach(function(key) { if (key.match(regexp)) { delete activeNodes[key]; } }); } else if (!this.isRootPaint()) { // no root painted, current is top level node, and need inactive slibings // clear all activeNodes = {}; } } activeNodes[nodeId] = node; // this.setState({activeNodes: activeNodes}); var _this = this; var previousActiveNodes = null; this.setState(function(previousState, currentProps) { previousActiveNodes = previousState.activeNodes; return {activeNodes: activeNodes}; }, function() { _this.notifyEvent({ type: 'expand', before: previousActiveNodes, after: activeNodes }); }); }, /** * get top level nodes * @returns {{}[]} */ getTopLevelNodes: function() { return this.getCodeTable().list(); }, /** * get avaiable top level nodes * @returns {CodeTable} */ getCodeTable: function() { return this.getComponentOption('data'); }, getNodeIcon: function(node, nodeId) { var isLeaf = this.isLeaf(node); // not leaf, must be a folder, or node is defined as folder var isFolder = !isLeaf || node.folder; var active = this.isActive(nodeId); if (isFolder) { if (isLeaf) { return this.getCustomNodeIcon({ node: node, active: active, folder: true, leaf: true }, NTree.FOLDER_ICON); } else if (active) { return this.getCustomNodeIcon({ node: node, active: true, folder: true, leaf: false }, NTree.FOLDER_OPEN_ICON); } else { return this.getCustomNodeIcon({ node: node, active: false, folder: true, leaf: false }, NTree.FOLDER_ICON); } } else { return this.getCustomNodeIcon({ node: node, active: active, folder: false, leaf: true }, NTree.FILE_ICON); } }, getNodeText: function(node) { return node.text; }, /** * get customized node icon * @param options {node: JSON, active: boolean, folder: boolean, leaf: boolean} */ getCustomNodeIcon: function(options, defaultIcon) { var icon = this.getComponentOption('nodeIcon'); if (typeof icon === 'function') { return icon.call(this, options); } else if (icon) { return icon; } else { return defaultIcon; } }, getNodeOperationIcon: function(node, nodeId) { var isLeaf = this.isLeaf(node); // not leaf, must be a folder, or node is defined as folder var isFolder = !isLeaf || node.folder; var active = this.isActive(nodeId); if (isFolder) { if (isLeaf) { return this.getCustomNodeOperationIcon({ node: node, active: active, folder: true, leaf: true }, NTree.OP_FOLDER_LEAF_ICON); } else { if (active) { return this.getCustomNodeOperationIcon({ node: node, active: true, folder: true, leaf: false }, NTree.OP_FOLDER_OPEN_ICON); } else { return this.getCustomNodeOperationIcon({ node: node, active: false, folder: true, leaf: false }, NTree.OP_FOLDER_ICON); } } } else { return this.getCustomNodeOperationIcon({ node: node, active: active, folder: false, leaf: true }, NTree.OP_FILE_ICON); } }, /** * get customized node operation icon * @param options {node: JSON, active: boolean, folder: boolean, leaf: boolean} */ getCustomNodeOperationIcon: function(options, defaultIcon) { var icon = this.getComponentOption('opNodeIcon'); if (typeof icon === 'function') { return icon.call(this, options); } else if (icon) { return icon; } else { return defaultIcon; } }, isNodeCanSelect: function(node) { var check = this.getComponentOption('check'); if (typeof check === 'function') { return check.call(this, node); } else if (check) { return check; } else { return false; } }, isNodeCheckCanChange: function(node) { var change = this.getComponentOption('valueCanCheck'); if (typeof change === 'function') { return change.call(this, node); } else if (change != null) { return change; } else { return true; } }, /** * is multiple selection allowed * @returns {boolean} */ isMultipleSelection: function() { return this.getComponentOption('multiple'); }, isMultipleHighlight: function() { return this.getComponentOption('multipleHightlight', false); }, /** * is hierarchy check, effective only when multiple is true * @returns {boolean} */ isHierarchyCheck: function() { return this.getComponentOption('hierarchyCheck') && this.isMultipleSelection(); }, getNodeId: function(parentNodeId, node) { var nodeId = null; if (parentNodeId) { nodeId = parentNodeId + NTree.NODE_SEPARATOR + node.id; } else { nodeId = '' + node.id; } return nodeId; } })); // expose to global $pt.Components.NTree = NTree; $pt.LayoutHelper.registerComponentRenderer($pt.ComponentConstants.Tree, function (model, layout, direction, viewMode) { return <$pt.Components.NTree {...$pt.LayoutHelper.transformParameters(model, layout, direction, viewMode)}/>; }); }(window, jQuery, React, ReactDOM, $pt));