UNPKG

reactui

Version:

A components library for ReactJS. This is part of the Gearz project

226 lines (194 loc) 8.68 kB
"use strict"; var React = require("react/addons"); var TreeRow = require("./treeRow.jsx"); var gearzMixin = require("../../gearz.mixin"); var TreeView = React.createClass({ displayName: "TreeView", mixins: [gearzMixin], propTypes: { nodes: React.PropTypes.object.isRequired }, /** * ReactJS function to get the component initial state. * @returns {{nodesCache: {}}} */ getInitialState: function getInitialState() { return { nodesCache: {} }; }, /** * Returns a list containing information about all nodes in a nodes collection. * @param nodes {Object | Array} * An object or array representing a nodes collection. * Each key of this object or array, contains a child node. * A child node may have an arbitrary set of properties, but children must be in a property called 'nodes'. * @param path {Array=} * Path to the node to which the passed nodes collection belongs. * @param output {Array=} * Optional array to which items will be pushed, and then returned. * If none is passed, then a new array is created, and returned. * @returns {Array} * An array containing information about all nodes in the tree analysed in pre-order. * Take a look at http://en.wikipedia.org/wiki/Tree_traversal#Pre-order */ flattenNodes: function flattenNodes(nodes, path, output) { var flatData = output || []; for (var key in nodes) if (nodes.hasOwnProperty(key)) { var info = { node: nodes[key], path: (path || []).concat(key) }; flatData.push(info); this.flattenNodes(info.node.nodes, info.path, flatData); } return flatData; }, /** * Gets the nodes corresponding to each key in a `path`. * E.g.: * `nodes` = {A: {B: {C: {}}}} * `path` = ["A", "B", "C"] * `return` => [{B: {C: {}}}] * @param nodes {Object | Array} * An object or array representing a nodes collection. * Each key of this object or array, contains a child node. * A child node may have an arbitrary set of properties, but children must be in a property called 'nodes'. * @param path {Array} * Path to get the nodes from. Each `path` key will be mapped to the corresponding node in the tree. * @param output {Array=} * Optional array to which items will be pushed, and then returned. * If none is passed, then a new array is created, and returned. * @returns {Array} * An array containing the nodes that correspond to each `path` key. */ getPathNodes: function getPathNodes(nodes, path, output) { var sequence = output || []; for (var it = 0; it < path.length; it++) { var key = path[it]; if (!nodes || !nodes.hasOwnProperty(key)) break; var node = nodes[key]; sequence.push(node); nodes = node.nodes; } return sequence; }, /** * Merges two nodes, the main node with all needed descendants and values, * and another source with default descendants and values. * @param main {Object|Array} * An object or array containing the main descendants and values, * that have precedence over the descendants and values from the default node. * @param defaults {Object|Array} * An object or array containing default descendants and values, * that are alternatives for elements missing from the main node. * @returns {Object|Array} * An object or array containing the merged descendants and values from the main node and the default node. */ mergeNodeValues: function mergeNodeValues(main, defaults) { var result = typeof main == "object" ? {} : Array.isArray(main) ? [] : null; if (!result) throw new Error("Argument `main` must be an object or an array."); if (typeof defaults != "object" && Array.isArray(defaults)) throw new Error("Argument `defaults` must be an object or an array."); for (var key2 in defaults) if (defaults.hasOwnProperty(key2) && key2 != "nodes") result[key2] = defaults[key2]; for (var key3 in main) if (main.hasOwnProperty(key3)) if (defaults.hasOwnProperty(key3) && key3 == "nodes") result[key3] = this.mergeNodeCollections(main[key3], defaults[key3]);else result[key3] = main[key3]; return result; }, /** * Merges two node-collections. * @param main {Object} * @param defaults {Object} * @returns {Object} */ mergeNodeCollections: function mergeNodeCollections(main, defaults) { var result = {}; for (var key1 in main) if (main.hasOwnProperty(key1)) if (defaults.hasOwnProperty(key1)) result[key1] = this.mergeNodeValues(main[key1], defaults[key1]);else result[key1] = main[key1]; return result; }, /** * Handles all node changes and triggers events indicating what node changed, * and what value of the node changed. * @param eventObject {Object} * An object containing information about the event. */ onNodeChange: function onNodeChange(eventObject) { function mergeOrCreate(previousValue) { return function (value) { return React.addons.update(typeof value != "object" ? {} : value, previousValue); }; } // Input: // eventObject.path = ["app","page","editPanel"] // eventObject.key = "collapsed" // eventObject.value = true // Output: // merger = {nodes: {$mergeOrCreate: {app: {$mergeOrCreate: {page: {$mergeOrCreate: {editPanel: {$mergeOrCreate: {collapsed: {$set: true}}}}}}}}}} var setter = {}; setter[eventObject.key] = { $set: eventObject.value }; var merger = eventObject.path.reduceRight(function (innerMerger, currentPathItem) { var itemMerger = {}; itemMerger[currentPathItem] = { $apply: mergeOrCreate(innerMerger) }; var nodesMerger = { nodes: { $apply: mergeOrCreate(itemMerger) } }; return nodesMerger; }, setter); // determining what is the new state var newState = React.addons.update(this.state, { nodesCache: merger.nodes }); this.setState(newState); // calling external event handlers if (eventObject.trigger(eventObject.genericEventName)) { return; }if (eventObject.trigger(eventObject.specificEventName)) { } }, /** * Determined whether a node visible or not. * A node is invisible when any ancestor node is collapsed. * @param nodes {Object|Array} * An object or array that represents the root node, from which path nodes will be taken. * @param path {Array} * An array containing the path components into the passed node. * @returns {boolean} * True if the node is hidden; otherwise false. */ isNodeHidden: function isNodeHidden(nodes, path) { var ancestors = this.getPathNodes(nodes, path); ancestors.pop(); return ancestors.map(function (x) { return x.collapsed; }).reduce(function (a, b) { return a || b; }, false); }, /** * ReactJS rendering function. * @returns {XML} */ render: function render() { var _this = this; var nodes = this.get("nodes"); var mergedNodes = this.mergeNodeCollections(nodes, this.state.nodesCache); var flattenNodes = this.flattenNodes(mergedNodes); var children = flattenNodes.map(function (info) { return _this.isNodeHidden(mergedNodes, info.path) ? null : React.createElement(TreeRow, { nodes: info.node.nodes, collapsed: info.node.collapsed, display: info.node.display, path: info.path, onAnyChange: function (e) { var newPath = Object.freeze([].concat(info.path)); var eventData = e.merge({ target: _this, path: newPath, specificEventName: "Node" + e.specificEventName }); _this.onNodeChange(eventData); } }); }); return React.createElement( "ul", { className: "list-group" }, children ); } }); module.exports = TreeView;