UNPKG

react-tree-menu

Version:
590 lines (493 loc) 19.1 kB
var React = require('react'), TreeMenu = require('../index'), TreeNode = require('../index').TreeNode, TreeMenuUtils = require('../index').Utils, Immutable = require('immutable'), _ = require('lodash'); var CSSTransitionGroup = require('react-addons-css-transition-group'); var App = React.createClass({ getInitialState: function() { return { lastAction1: null, lastAction2: null, lastAction3: null, staticTreeData: { "option_2.a" : { checked: false }, "option_2.b" : { checked: false } }, dynamicTreeData: [ { label : "Option 1" }, { label : "Option 2", children : [ { checkbox: true, label: "Sub Option A", children: [ { label: "Third Level Option 1", checkbox : true }, { label: "Third Level Option 2", checkbox : true } ] }, { checkbox: true, label: "Sub Option B" } ] } ], dynamicTreeDataMap: { "Option 1" : { checked: true, checkbox: true, children: { "Sub Option 1" : { checked: false }, "Sub Option 2" : { checked: false, checkbox: true, children: { "Sub-Sub Option 1" : { checked: false, checkbox: true }, "Sub-Sub Option 2" : { checked: false, checkbox: true } } } } }, "Option 2" : { checked: false, checkbox: true } }, dynamicTreeDataMap2: { "Option 1" : { checkbox: false, children: { "Sub Option 1" : { checkbox: false }, "Sub Option 2" : { checkbox: false, children: { "Sub-Sub Option 1" : { selected: true, checkbox: false }, "Sub-Sub Option 2" : { checkbox: false } } } } }, "Option 2" : { checkbox: false } } }; }, render: function() { var dynamicExample1 = this._getExamplePanel("Dynamic", this._getDynamicTreeExample()), declarativeExample = this._getExamplePanel("Declarative", this._getDeclarativeTreeExample()), unboundExample = this._getExamplePanel("Unbound", this._getUnboundTreeExample()), statefulExample = this._getExamplePanel("Stateful", this._getStatefulTreeExample()), dynamicExample2 = this._getExamplePanel("Dynamic (Object)", this._getDynamicTreeExample2()), dynamicExample3 = this._getExamplePanel("Selection w/o Checkboxes", this._getDynamicTreeExample3()), labelFilterExample = this._getExamplePanel("Label Filter", this._getLabelFilterExample()), rendererExample = this._getExamplePanel("Label/Checkbox Renderers", this._getRendererExample()); return <div className="container"> <div className="row"> <div className="col-lg-12 hero"> <h1><code>{"<TreeMenu/>"}</code></h1> </div> </div> <div className="row"> <div className="col-lg-12 hero"> <h3>Check out the code for this demo <a href="https://github.com/MandarinConLaBarba/react-tree-menu/blob/master/example/App.jsx">here</a>.</h3> </div> </div> <div className="row"> <div className="col-lg-3"> <h2>Dynamic (Array)</h2> <ul> <li>This menu is built dynamically using the data prop (array)</li> <li>It is collapsible (the default), so expand/collapse icons show</li> <li>It has no identifier prop for the TreeMenu, so it uses the array index when emitting events</li> </ul> </div> <div className="col-lg-3"> <h2>Declarative</h2> <ul> <li>This menu is built w/ nested TreeNode components</li> <li>It has collapsible=false, so no expand/collapse icons show</li> <li>It has identifier="id", so it uses the 'id' prop when emitting events</li> </ul> </div> <div className="col-lg-3"> <h2>{"Unbound: Won't Update UI State"}</h2> <ul> <li>This menu doesn't have any handlers applied that update UI state, so it's static.</li> </ul> </div> <div className="col-lg-3"> <h2>{"Stateful, manages its own UI state"}</h2> <ul> <li>This menu has stateful=true, so it manages its own state w/o help from the Controller-View.</li> </ul> </div> </div> <div className="row"> <div className="col-lg-3"> {dynamicExample1} </div> <div className="col-lg-3"> {declarativeExample} </div> <div className="col-lg-3"> {unboundExample} </div> <div className="col-lg-3"> {statefulExample} </div> </div> <div className="row"> <div className="col-lg-3"> <CSSTransitionGroup transitionEnterTimeout={500} transitionName="last-action" transitionLeave={false}> {this._getLastActionNode("1")} </CSSTransitionGroup> </div> <div className="col-lg-3"> <CSSTransitionGroup transitionEnterTimeout={500} transitionName="last-action" transitionLeave={false}> {this._getLastActionNode("2")} </CSSTransitionGroup> </div> <div className="col-lg-3"> <CSSTransitionGroup transitionEnterTimeout={500} transitionName="last-action" transitionLeave={false}> {this._getLastActionNode("3")} </CSSTransitionGroup> </div> <div className="col-lg-3"> <CSSTransitionGroup transitionEnterTimeout={500} transitionName="last-action" transitionLeave={false}> {this._getLastActionNode("4")} </CSSTransitionGroup> </div> </div> <div className="row"> <div className="col-lg-3"> <h2>Dynamic (Object)</h2> <ul> <li>This menu is built dynamically using the data prop (object)</li> <li>It is collapsible (the default), so expand/collapse icons show</li> <li>It has no identifier prop for the TreeMenu, so it uses the object key when emitting events</li> </ul> </div> <div className="col-lg-3"> <h2>Dynamic - No Checkboxes</h2> <ul> <li>This menu is built dynamically using the data prop (object)</li> <li>It doesn't have checkboxes, but does have selection state</li> </ul> </div> <div className="col-lg-3"> <h2>Label Filter</h2> <ul> <li>This menu has a labelFilter prop that truncates the displayed label</li> <li>NOTE: no event handlers attached for this example</li> </ul> </div> <div className="col-lg-3"> <h2>Renderers</h2> <ul> <li>Using renderer (node factory) methods as arguments</li> <li>NOTE: no event handlers attached for this example</li> </ul> </div> </div> <div className="row"> <div className="col-lg-3"> {dynamicExample2} </div> <div className="col-lg-3"> {dynamicExample3} </div> <div className="col-lg-3"> {labelFilterExample} </div> <div className="col-lg-3"> {rendererExample} </div> </div> <div className="row"> <div className="col-lg-3"> <CSSTransitionGroup transitionEnterTimeout={500} transitionName="last-action" transitionLeave={false}> {this._getLastActionNode("5")} </CSSTransitionGroup> </div> <div className="col-lg-3"> <CSSTransitionGroup transitionEnterTimeout={500} transitionName="last-action" transitionLeave={false}> {this._getLastActionNode("6")} </CSSTransitionGroup> </div> </div> </div>; }, _getLastActionNode: function (col) { var lastActionNode = <div className="text-center alert alert-success tree-event-alert">{"Waiting for interaction"}</div>, key = "lastAction" + col; var action = this.state[key]; if (action) { lastActionNode = ( <div className="text-center alert alert-success tree-event-alert" key={"lastAction_" + col + "_" + action.time}> <h3>Event Received:</h3> <div><strong>{action.event}</strong></div> <h3><code>{"<TreeNode/>"}</code> Affected: </h3> <div><strong>{action.node}</strong></div> </div>); } else { } return lastActionNode; }, _getStatefulTreeExample: function () { return ( <TreeMenu identifier="id" stateful={true} onTreeNodeClick={this._setLastActionState.bind(this, "clicked", "4")} onTreeNodeCollapseChange={this._setLastActionState.bind(this, "collapsed", "4")} onTreeNodeCheckChange={this._setLastActionState.bind(this, "checked", "4")} expandIconClass="fa fa-chevron-right" collapseIconClass="fa fa-chevron-down"> <TreeNode label="Option 1" checkbox={true} id="option_1"/> <TreeNode label="Option 2" checkbox={true} id="option_2"> <TreeNode label="Option A" checkbox={true} id="option_2.a"/> <TreeNode label="Option B" checkbox={true} id="option_2.b"> <TreeNode label="Option B.x" checkbox={true} id="option_2.b.x"/> <TreeNode label="Option B.y" checkbox={true} id="option_2.b.y"/> </TreeNode> </TreeNode> </TreeMenu> ); }, _getUnboundTreeExample: function () { return ( <TreeMenu identifier="id" onTreeNodeClick={this._setLastActionState.bind(this, "clicked", "3")} onTreeNodeCollapseChange={this._setLastActionState.bind(this, "collapsed", "3")} onTreeNodeCheckChange={this._setLastActionState.bind(this, "checked", "3")} expandIconClass="fa fa-chevron-right" collapseIconClass="fa fa-chevron-down"> <TreeNode label="Option 1" checkbox={true} id="option_1"/> <TreeNode label="Option 2" checkbox={true} id="option_2"> <TreeNode label="Option A" checkbox={true} checked={true} id="option_2.a"/> <TreeNode label="Option B" checkbox={true} id="option_2.b"/> </TreeNode> <TreeNode label="Option 3" checkbox={true} id="option_3"/> <TreeNode label="Option 3" checkbox={true} id="option_3"/> </TreeMenu> ); }, _getDeclarativeTreeExample: function () { return ( <TreeMenu sort identifier="id" onTreeNodeClick={this._setLastActionState.bind(this, "clicked", "2")} onTreeNodeCollapseChange={this._handleDeclarativeTreeNodePropChange.bind(this, "collapsed")} onTreeNodeCheckChange={this._handleDeclarativeTreeNodePropChange.bind(this, "checked")} collapsible={false} expandIconClass="fa fa-chevron-right" collapseIconClass="fa fa-chevron-down"> <TreeNode label="Option 1" id="option_1"/> <TreeNode label="Option 2" id="option_2"> <TreeNode label="Option B" checkbox={true} checked={this.state.staticTreeData["option_2.b"].checked} id="option_2.b"/> <TreeNode label="Option A" checkbox={true} checked={this.state.staticTreeData["option_2.a"].checked} id="option_2.a"/> </TreeNode> <TreeNode label="Option 3" id="option_3"/> <TreeNode label="Option 4" id="option_4"/> </TreeMenu> ); }, _getLabelFilterExample: function () { return ( <TreeMenu identifier="id" onTreeNodeClick={function() {}} onTreeNodeCollapseChange={function() {}} onTreeNodeCheckChange={function() {}} collapsible={false} labelFilter={function(label) { var max = 10; if (label.length <= max) return label; return label.substring(label, max).concat("..."); }} expandIconClass="fa fa-chevron-right" collapseIconClass="fa fa-chevron-down"> <TreeNode label="Option 1 with a long name" id="option_1"/> <TreeNode label="Option 2 also has a long name" id="option_2"> <TreeNode label="Option A with a long name" checkbox={true} id="option_2.a"/> <TreeNode label="Option B with a long name" checkbox={true} id="option_2.b"/> </TreeNode> <TreeNode label="Option 3 with a long name" id="option_3"/> <TreeNode label="Option 4 with a long name" id="option_4"/> </TreeMenu> ); }, _getRendererExample: function () { return ( <TreeMenu identifier="id" onTreeNodeClick={function() {}} onTreeNodeCollapseChange={function() {}} onTreeNodeCheckChange={function() {}} collapsible={false} labelFilter={function(label) { var max = 10; if (label.length <= max) return label; return label.substring(label, max).concat("..."); }} labelFactory={function(labelClassName, displayLabel) { return <span className={labelClassName}>{displayLabel} {"<span/>"}</span>; }} checkboxFactory={function(checkboxClassName, isChecked) { return <span className={checkboxClassName + " fa " + (isChecked ? "fa-check-circle" : "fa-circle-o")}></span>; }} expandIconClass="fa fa-chevron-right" collapseIconClass="fa fa-chevron-down"> <TreeNode label="Option 1 with a long name" id="option_1"/> <TreeNode label="Option 2 also has a long name" id="option_2" checkbox={true} checked={false}> <TreeNode label="Option A with a long name" checkbox={true} id="option_2.a"/> <TreeNode label="Option B with a long name" checkbox={true} checked={true} id="option_2.b"/> </TreeNode> <TreeNode label="Option 3 with a long name" id="option_3"/> <TreeNode label="Option 4 with a long name" id="option_4"/> </TreeMenu> ); }, _getDynamicTreeExample: function () { return ( <TreeMenu expandIconClass="fa fa-chevron-right" collapseIconClass="fa fa-chevron-down" onTreeNodeClick={this._setLastActionState.bind(this, "clicked", "1")} onTreeNodeCollapseChange={this._handleDynamicTreeNodePropChange.bind(this, "collapsed")} onTreeNodeCheckChange={this._handleDynamicTreeNodePropChange.bind(this, "checked")} data={this.state.dynamicTreeData} /> ); }, _getDynamicTreeExample2: function () { return ( <TreeMenu expandIconClass="fa fa-chevron-right" collapseIconClass="fa fa-chevron-down" onTreeNodeClick={this._setLastActionState.bind(this, "clicked", "5")} onTreeNodeCollapseChange={this._handleDynamicObjectTreeNodePropChange.bind(this, 5, "dynamicTreeDataMap","collapsed")} onTreeNodeCheckChange={this._handleDynamicObjectTreeNodePropChange.bind(this, 5, "dynamicTreeDataMap","checked")} data={this.state.dynamicTreeDataMap} /> ); }, _getDynamicTreeExample3: function () { return ( <TreeMenu expandIconClass="fa fa-chevron-right" collapseIconClass="fa fa-chevron-down" onTreeNodeCollapseChange={this._handleDynamicObjectTreeNodePropChange.bind(this, 6, "dynamicTreeDataMap2", "collapsed")} onTreeNodeCheckChange={this._handleDynamicObjectTreeNodePropChange.bind(this, 6, "dynamicTreeDataMap2","checked")} onTreeNodeSelectChange={this._handleDynamicObjectTreeNodePropChange.bind(this, 6, "dynamicTreeDataMap2","selected")} data={this.state.dynamicTreeDataMap2} /> ); }, _getExamplePanel: function (title, treeMenuNode) { return <div> <div className="panel panel-default"> <div className="panel-heading">{title + " Menu"}</div> <div className="panel-body"> {treeMenuNode} </div> </div> </div>; }, _handleDeclarativeTreeNodePropChange: function (propName, lineage) { this._setLastActionState(propName, "2", lineage); var nodeId = lineage.pop(); var treeState = this.state.staticTreeData; treeState[nodeId][propName] = !treeState[nodeId][propName]; this.setState({ staticTreeData : treeState }); }, _handleDynamicTreeNodePropChange: function (propName, lineage) { this._setLastActionState(propName, "1", lineage); this.setState(TreeMenuUtils.getNewTreeState(lineage, this.state.dynamicTreeData, propName)); }, _handleDynamicObjectTreeNodePropChange: function (messageWindowKey, stateKey, propName, lineage) { this._setLastActionState(propName, messageWindowKey, lineage); //Get a node path that includes children, given a key function getNodePath(nodeKey) { if (nodeKey.length === 1) return nodeKey; return _(nodeKey).zip(nodeKey.map(function () { return "children"; })).flatten().initial().value(); } var oldState = Immutable.fromJS(this.state[stateKey]); var nodePath = getNodePath(lineage), keyPaths = [nodePath.concat([propName])]; //Build a list of key paths for all children function addChildKeys(state, parentPath) { var childrenPath = parentPath.concat('children'), children = state.getIn(childrenPath); if (!children || children.size === 0) return; children.map(function (value, key) { keyPaths.push(childrenPath.concat([key, propName])) addChildKeys(state, childrenPath.concat([key])); }); } addChildKeys(oldState, nodePath); //Get the new prop state var newPropState = !oldState.getIn(keyPaths[0]); //Now create a new map w/ all the changes var newState = oldState.withMutations(function(state) { keyPaths.forEach(function (keyPath) { state.setIn(keyPath, newPropState); }) }); var mutation = {}; mutation[stateKey] = newState.toJS(); this.setState(mutation); }, _setLastActionState: function (action, col, node) { var toggleEvents = ["collapsed", "checked", "selected"]; if (toggleEvents.indexOf(action) >= 0) { action += "Changed"; } console.log("Controller View received tree menu " + action + " action: " + node.join(" > ")); var key = "lastAction" + col; var mutation = {}; mutation[key] = { event: action, node: node.join(" > "), time: new Date().getTime() }; this.setState(mutation) } }); module.exports = App;