Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding event handlers to rowRenderer elements. #1

Closed
confusingstraw opened this issue May 17, 2016 · 8 comments
Closed

Adding event handlers to rowRenderer elements. #1

confusingstraw opened this issue May 17, 2016 · 8 comments

Comments

@confusingstraw
Copy link

Hi, I was just wondering if there was a preferred way to listen for events in the HTML string returned by a custom rowRenderer.

If it was an option, I'd just return a premade Element, but it looks like only strings are accepted, and as such, I would need to use some kind of event delegation to be able to listen for something like a click on the element.

One example of a use case for this would be to have a "+" or "-" depending on node.state.open, and to call tree.openNode or tree.closeNode when it is clicked.

If there is an existing feature that allows this, I'd be happy to use it.

Thanks.

@cheton
Copy link
Owner

cheton commented May 18, 2016

The infinite-tree depends on Clusterize.js to display large data set, and it only accepts HTML string, so you cannot return an element within your rowRenderer.

To attach event listeners for nodes, I'd suggest you use event delegation, it allows you to avoid adding event listeners to specific nodes, or it will cause performance issues while handling large data sets.

See event delegation examples as shown below:

Example 1: Pure JavaScript

// JavaScript
var el = document.getElementById('tree');
var tree = new InfiniteTree(el, { /* options */ });

el.querySelector('.infinite-tree-content').onclick = function(event) {
    event = event || window.event;
    var target = event.target || event.srcElement;

    console.log(target);

    if (!hasClass(target, 'my-specific-class')) {
        return;
    }

    // do stuff with node
};

// Checks if an element contains a specific class
var hasClass = function(el, className) {
    if (!el) {
        return false;
    }
    var classes = el.className.split(' ');
    return (classes.indexOf(className) >= 0);
};

Example 2: Use jQuery

// jQuery
$('#tree .infinite-tree-content').on('click', 'your-event-selector', function() {
  // do stuff with node
});

For the use case of handling '+' and '-' that you mentioned, it's not necessary to handle the click event yourself to call tree.openNode or tree.closeNode. You can pass the togglerClass option (it defaults to infinite-tree-toggler) to specify the toggler class using in your rowRenderer.

var tree = new InfiniteTree({
    togglerClass: 'infinite-tree-toggler'
});

The infinite-tree library use the togglerClass to determine if the mouse is clicking on the toggler to open/close a tree node. Check the source code below for details:
https://github.com/cheton/infinite-tree/blob/master/src/infinite-tree.js#L77

@cheton
Copy link
Owner

cheton commented May 18, 2016

Here is an example of custom rowRenderer using the togglerClass option:
https://github.com/cheton/infinite-tree/blob/master/examples/classic/renderer.js#L29

@cheton
Copy link
Owner

cheton commented May 18, 2016

If you're using React, you can import { renderToString } from 'react-dom/server' to render a React element into HTML string.

The sample code shown below is a direct translation from the original custom rowRenderer example:
https://github.com/cheton/react-infinite-tree/blob/master/examples/renderer.js

import React from 'react';
import ReactDOMServer from 'react-dom/server';
import classNames from 'classnames';

const renderer = (node, treeOptions) => {
    const { id, name, loadOnDemand = false, children, state, props = {} } = node;
    const droppable = (treeOptions.droppable) && (props.droppable);
    const { depth, open, path, total, loading = false, selected = false } = state;
    const childrenLength = Object.keys(children).length;
    const more = node.hasChildren();

    return ReactDOMServer.renderToString(
        <div
            className={classNames(
                'infinite-tree-item',
                { 'infinite-tree-selected': selected }
            )}
            data-id={id}
            data-expanded={more && open}
            data-depth={depth}
            data-path={path}
            data-selected={selected}
            data-children={childrenLength}
            data-total={total}
            droppable={droppable}
        >
            <div
                className="infinite-tree-node"
                style={{ marginLeft: depth * 18 }}
            >
                <a
                    className={(() => {
                        if (!more && loadOnDemand) {
                            return classNames(treeOptions.togglerClass, 'infinite-tree-closed');
                        }
                        if (more && open) {
                            return classNames(treeOptions.togglerClass);
                        }
                        if (more && !open) {
                            return classNames(treeOptions.togglerClass, 'infinite-tree-closed');
                        }
                        return '';
                    })()}
                >
                    {!more && loadOnDemand &&
                        <i className="glyphicon glyphicon-triangle-right" />
                    }
                    {more && open &&
                        <i className="glyphicon glyphicon-triangle-bottom" />
                    }
                    {more && !open &&
                        <i className="glyphicon glyphicon-triangle-right" />
                    }
                </a>

                <i
                    className={classNames(
                        'infinite-tree-folder-icon',
                        'glyphicon',
                        { 'glyphicon-folder-open': more && open },
                        { 'glyphicon-folder-close': more && !open },
                        { 'glyphicon-file': !more }
                    )}
                >
                </i>

                <span className="infinite-tree-title">{name}</span>

                <i
                    style={{ marginLeft: 5 }}
                    className={classNames(
                        { 'hidden': !loading },
                        'glyphicon',
                        'glyphicon-refresh',
                        { 'rotating': loading }
                    )}
                />

                <span className="count">{childrenLength}</span>
            </div>
        </div>
    );
};

export default renderer;

@confusingstraw
Copy link
Author

confusingstraw commented May 18, 2016

Thanks for the response!

After a quick look at clusterize on the link you provided, it looks like they provide the ability to use existing markup with scrollElem instead of the rows option.

Just for the sake of interest, I'd recommend checking out react-virtualized, since it seems to aim to solve many of the same problems.

@cheton
Copy link
Owner

cheton commented May 18, 2016

Clusterize.js provides the contentElem option to load DOM nodes during initialization, once it has finished its initialization, it will use a sliding buffer mechanism by removing invisible DOM nodes from HTML to provide good scrolling performance, thus you will not be able to attach event listeners to specific DOM elements. In such a case, event delegation is the only option to listen events for DOM elements with infinite scrolling support.

The event delegation example I provided above is a bit cumbersome because it has to query the .infinite-tree-content element to attach an event listener, I can provide a click event for the tree to make things easier. For example:

var tree = new InfiniteTree(el, options);
tree.on('click', function(event) {
    var target = event.target || event.srcElement; // IE8
    // do stuff with node
});

cheton added a commit that referenced this issue May 20, 2016
@cheton
Copy link
Owner

cheton commented May 21, 2016

The click event is provided in v1.2.0. I also wrapped event.stopPropagation that allows you to stop subsequent executions (e.g. selectNode, openNode, closeNode) as needed.

var tree = new InfiniteTree(el, options);
tree.on('click', function(event) {
    var target = event.target || event.srcElement; // IE8

    // Call event.stopPropagation() if you want to prevent the execution of
    // default tree operations like selectNode, openNode, and closeNode.
    event.stopPropagation();

    // do stuff with the target element.
    console.log(target);
});

@cheton cheton closed this as completed May 21, 2016
@aalbul
Copy link
Contributor

aalbul commented Jun 15, 2016

Gosh. This must be somewhere in documentation. I spent quiet a lot of time trying to figure out what's going on with my events :)

@cheton
Copy link
Owner

cheton commented Jun 15, 2016

Thanks! I just updated the sample code in FAQ and Wiki page.

https://github.com/cheton/infinite-tree#faq
https://github.com/cheton/infinite-tree/wiki/Events#click

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants