swiftcarrot / react-ui-tree

React tree component with drag & drop
https://swiftcarrot.github.io/react-ui-tree/
MIT License
761 stars 195 forks source link

Lazy loading #16

Open edgesoft opened 9 years ago

edgesoft commented 9 years ago

I have a very large tree that I won't be able to load on initialization. Would it be possible to do lazy loading? A user clicks on a node and fetches data from server and adds it to the tree. We would need some way of telling the node that it has children but it should be loaded from the server.

wangzuo commented 9 years ago

How large is the tree?

edgesoft commented 9 years ago

More than 10.000 nodes ( Not for every user. Some has just 100 ). All won't expand every time a user works in the tree.

dannav commented 9 years ago

Are you not able to do that in renderNode? You could update the tree when a node is selected.

edgesoft commented 9 years ago

Let's say I want to move a node with many children to another nodes child. I need to expand the node on hover and then fetch data from server. And then be able to hover yet another node that was just fetched and fetch until the user finds which node he/she wants to move to. I need to refresh on hover and expand lazily. I don't know how to accomplish that right now.

edgesoft commented 9 years ago

@wangzuo Proposing to add lazy boolean to the tree. If the tree is lazy then we should check index.hasChildren and not just index.children && index.children.length. An onBeforeOpenNode event should be called in toggleCollapse to allow fetching data from server before we expand.

{ module: 'parent', leaf: false, hasChildren: true }
edgesoft commented 9 years ago

Something like this so far. This will enable lazyloading per node.

modules

{
   module: 'test',
   leaf: false,
   hasChildren: true,
   collapsed: true,
   lazy: true
}

react-ui-tree.js

 toggleCollapse(nodeId) {
    var tree = this.state.tree;
    var index = tree.getIndex(nodeId);
    var node = index.node;
    if( this.props.onLazyloadNode && node.lazy && node.hasChildren ){
      node.lazy = false;
      this.props.onLazyloadNode(node, function(nodes){
          nodes.map( newNode => {
             tree.append(newNode,nodeId);
          } );
         this.makeToggleUpdate(node);
      }.bind(this));
    }else{
      this.makeToggleUpdate(node);
    }
  },

  makeToggleUpdate(node){
    var tree = this.state.tree;
    node.collapsed = !node.collapsed;
    tree.updateNodesPosition();

    this.setState({
      tree: tree
    });

    this.change(tree);
  }

node.js. Updated node.hasChildren

renderCollapse() {
    var index = this.props.index;
    var node = index.node;
    if(node.hasChildren || (index.children && index.children.length)) {
      var collapsed = index.node.collapsed;

      return (
        <span
          className={cx('collapse', collapsed ? 'caret-right' : 'caret-down')}
          onMouseDown={function(e) {e.stopPropagation()}}
          onClick={this.handleCollapse}>
        </span>
      );
    }

    return null;
  },

exampel app.js


  <Tree
       paddingLeft={20}
       tree={this.state.tree}
       onChange={this.handleChange}
       isNodeCollapsed={this.isNodeCollapsed}
       renderNode={this.renderNode}
       shouldRenderRootNode={true}
       onLazyloadNode={this.onLazyloadNode}
   />

  onLazyloadNode(node,cb){
    console.log( "Before expand" );
    setTimeout(function(){
      var nodes  = [
        {module: 'mathias', leaf: false, collapsed: true, lazy: true,hasChildren : true}
      ];
      cb( nodes );
      console.log( "After expand" );
    },2000);

  },
tobyndockerill commented 9 years ago

This seems like a good improvement that will become more necessary as the amount of nodes increase. I have a project that has around 5000 nodes and it can get quite slow.

Should the suggested changes be in a pull request?

wangzuo commented 9 years ago

Thanks! will try to add this feature.

edgesoft commented 9 years ago

@wangzuo I didn't commit this yet but accepted the pull request from @tobyndockerill because there is still a problem to solve. When clicking the expand arrow multiple times we should have a behaviour. What should happen? Right now we set the lazy to false so that we don't fetch data from server again. But the toggle is slow an feels unresponsive. Should we lock the node currently lazyloading to prevent toogle when loading? Have a spinner? any suggestions?

edgesoft commented 9 years ago

The best suggestion so far is to lock click on the arrow and show a svg spinner instead of the arrow. Is this a good solution? @wangzuo @tobyndockerill

tobyndockerill commented 9 years ago

I agree. A loading indicator would go a long way to show the user that something is happening behind the scenes.

rohan-deshpande commented 9 years ago

I am also super interested in this, drag and drop becomes extremely slow even in a moderately sized tree

mofadlalla commented 8 years ago

I'm interested in this too, any chance there's going to be a PR for this soon?

wizzardich commented 7 years ago

I'd love to see this feature implemented as well. It would help immensely, and it is a very nice thing to have anyway.