1
\$\begingroup\$

DataTree

A React-based expandable list using ChimeraZen's Zen Design styles.

This program simulates items returned from a Google Firebase database, populates an array of recursively nested parent/children arrays based off of the item title and parent, recursively sorts into ascending order, and produces a recursively nested <ul> that can be expanded when the tree-nav icon is clicked.

Questions:

  1. Are there any comments on the UX, UI, styling, or general feel?
  2. How can I improve upon this program?

/**
* Name:        DataTree
* Description: A React-based expandable list using Zen Design styles
* @package     Chimera Apps
* @version     1.0.1
* @author      Chimera.Zen
* @copyright   Copyright (c) 2018, Chimera.Zen
* @link        https://github.com/ChimeraZen
* @license     http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*/

class DataTree extends React.Component {
	constructor(props) {
  	super(props)
    this.handleExpand = this.handleExpand.bind(this)
  }
  
  handleExpand(e) {
  	e.target.parentNode.classList.toggle('expand')
  }
  
  inTree_r(needle, haystack) {
  	for(let i in haystack) {
    	const stack = haystack[i]
      if(needle === stack.title) {
      	return true
      } else {
      	if(stack.children.length > 0) {
        	this.inTree_r(needle, stack.children)
        }
      }
      return false
    }
  }

  pushTree_r(needle, parent, haystack) {
  	for(let i in haystack) {
    	const stack = haystack[i]
    	if(parent === stack.title) {
      	stack.children.push(needle)
      } else {
      	this.pushTree_r(needle, parent, stack.children)
      }
    }
  }
  
  sortTree_r(haystack) {
  	for(let i in haystack) {
      const stack = haystack[i]
      haystack.sort((a,b) => a.title > b.title)
      if(stack.children.length > 0) {
        this.sortTree_r(stack.children)
      }
    }
  }

  buildTree(roots) {
    const tree = (
      <ul className="data-tree">
        {roots.map(branch => {
          return (
            <li key={branch.title} className={branch.children.length > 0 ? "data-container" : null}>
              {
                branch.children.length > 0 ? 
                <i className="fas fa-chevron-right tree-nav" onClick={this.handleExpand}></i> 
                :
                  <i></i>
              }
              <p>{branch.title}</p>
              {branch.children.length > 0 && this.buildTree(branch.children)}
            </li>
          )
        })}
      </ul>
    )
    return tree
  }

  getTree(list, topLevel=false) {
    const roots = []
    for(let i in list) {
      if(!this.inTree_r(list[i].title, roots)) {
        list[i].children = []
        if(list[i].parent === topLevel) {
          roots.push(list[i])
        } else {
          this.pushTree_r(list[i], list[i].parent, roots)
        }
      }
    }
    this.sortTree_r(roots)
    const tree = this.buildTree(roots)
    return tree
  }
  
	render () {
  	return (
    	<div className="data-trunk">
        <DataTreeHeader>
          {this.props.header}
        </DataTreeHeader>
        {// Optional: second parameter 'root' is used to define the top-level
        this.getTree(this.props.list)
        }
      </div>
    )
  }
}

const DataTreeHeader = (props) => {
	return (
  	<h2>{props.children}</h2>
  )
}

class App extends React.Component {
  render() {
  	const list = [
      {
		title: 'Archery',
        parent: false						// parent is false if list item is top-level
      },
      {
		title: 'Blacksmithing',
        parent: false
      },
      {
		title: 'Arrowheads',
        parent: 'Archery'
      },
      {
		title: 'Shafts',
        parent: 'Archery'
      },
      {
		title: 'Carbon Fiber',
        parent: 'Shafts'
      },
      {
		title: 'Plastic',
        parent: 'Shafts'
      },
      {
		title: 'Tongs',
        parent: 'Blacksmithing'
      },
      {
		title: 'Hammers',
        parent: 'Blacksmithing'
      },
      {
		title: 'Broadhead',
        parent: 'Arrowheads'
      },
      {
		title: 'Baseball',
        parent: false
      }
    ]
    
    return (
    	<DataTree header="Categories" list={list} />
    )
  }
}

ReactDOM.render(<App />, document.querySelector('#app'))
* {
  padding: 0;
  margin: 0;
}

body {
  background: #20262E;
  padding: 20px;
  font-family: Helvetica;
}

.data-trunk {
  display: inline-block;
  background: #fff;
  border-radius: 4px;
  padding: 20px;
  transition: all 0.2s;
}

.data-trunk h2 {
  font-weight: bold;
  border-bottom: 3px solid green;
}

.data-trunk>.data-tree {
  margin-left: 0;
}

.data-tree {
  margin-left: 20px;
}

.data-tree li {
  list-style-type: none;
}

.data-tree li>i {
  display: inline-flex !important;
  justify-content: center;
  align-items: center;
  width: 20px;
  height: 20px;
  font-size: 13px;
  vertical-align: middle;
  transition: transform .25s ease-in;
}

.data-tree li p {
  display: inline-block;
  padding: 5px 3px 5px 5px;
  border-left: 2px solid green;
}

.data-tree .data-container {
  overflow: hidden;
}

.data-tree .data-container .data-tree {
  max-height: 0;
  margin-left: 20px;
  transition: max-height 0.5s cubic-bezier(0, 1, 0, 1);
  border-left: 2px solid #ddd;
}

.data-tree .data-container.expand>i {
  transform: rotate(90deg);
  transition: transform .25s ease-out;
}

.data-tree .data-container.expand>.data-tree {
  max-height: 1000px;
  transition: max-height 0.8s ease-in-out;
}
<link href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.