{"id":18910062,"url":"https://github.com/cheton/infinite-tree","last_synced_at":"2025-04-04T16:17:27.561Z","repository":{"id":6188357,"uuid":"54970261","full_name":"cheton/infinite-tree","owner":"cheton","description":"A browser-ready tree library that can efficiently display a large amount of data using infinite scrolling.","archived":false,"fork":false,"pushed_at":"2024-06-14T05:16:21.000Z","size":8729,"stargazers_count":168,"open_issues_count":23,"forks_count":38,"subscribers_count":11,"default_branch":"master","last_synced_at":"2024-10-30T00:00:13.560Z","etag":null,"topics":["event-delegation","infinite-scroll","tree"],"latest_commit_sha":null,"homepage":"https://infinite-tree.js.org","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cheton.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":null,"patreon":null,"open_collective":"cheton","ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2016-03-29T11:28:25.000Z","updated_at":"2024-09-10T14:21:14.000Z","dependencies_parsed_at":"2024-11-08T09:50:45.212Z","dependency_job_id":null,"html_url":"https://github.com/cheton/infinite-tree","commit_stats":{"total_commits":607,"total_committers":10,"mean_commits":60.7,"dds":0.03130148270181221,"last_synced_commit":"4938a45956cb00b8fe8ca7dc63669b347a8717ae"},"previous_names":[],"tags_count":71,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cheton%2Finfinite-tree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cheton%2Finfinite-tree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cheton%2Finfinite-tree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cheton%2Finfinite-tree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cheton","download_url":"https://codeload.github.com/cheton/infinite-tree/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245276935,"owners_count":20589058,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["event-delegation","infinite-scroll","tree"],"created_at":"2024-11-08T09:39:18.331Z","updated_at":"2025-03-28T15:11:20.509Z","avatar_url":"https://github.com/cheton.png","language":"JavaScript","readme":"# Infinite Tree [![build status](https://travis-ci.org/cheton/infinite-tree.svg?branch=master)](https://travis-ci.org/cheton/infinite-tree) [![Coverage Status](https://coveralls.io/repos/github/cheton/infinite-tree/badge.svg?branch=master)](https://coveralls.io/github/cheton/infinite-tree?branch=master)\n[![NPM](https://nodei.co/npm/infinite-tree.png?downloads=true\u0026stars=true)](https://www.npmjs.com/package/infinite-tree)\n\nA browser-ready tree library that can efficiently display a large tree with smooth scrolling.\n\nDemo: http://cheton.github.io/infinite-tree\n\n[![infinite-tree](https://raw.githubusercontent.com/cheton/infinite-tree/master/media/infinite-tree.gif)](http://cheton.github.io/infinite-tree)\n\n## Features\n* High performance infinite scroll with large data set\n* [Customizable renderer](https://github.com/cheton/infinite-tree/wiki/Options#rowrenderer) to render the tree in any form\n* [Load nodes on demand](https://github.com/cheton/infinite-tree/wiki/Options#loadnodes)\n* Native HTML5 drag and drop API\n* A rich set of [APIs](https://github.com/cheton/infinite-tree#api-documentation)\n* No jQuery\n\n## Browser Support\n![Chrome](https://github.com/alrra/browser-logos/raw/master/src/chrome/chrome_48x48.png)\u003cbr\u003eChrome | ![Edge](https://github.com/alrra/browser-logos/raw/master/src/edge/edge_48x48.png)\u003cbr\u003eEdge | ![Firefox](https://github.com/alrra/browser-logos/raw/master/src/firefox/firefox_48x48.png)\u003cbr\u003eFirefox | ![IE](https://github.com/alrra/browser-logos/raw/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png)\u003cbr\u003eIE | ![Opera](https://github.com/alrra/browser-logos/raw/master/src/opera/opera_48x48.png)\u003cbr\u003eOpera | ![Safari](https://github.com/alrra/browser-logos/raw/master/src/safari/safari_48x48.png)\u003cbr\u003eSafari\n--- | --- | --- | --- | --- | --- |\n Yes | Yes | Yes| 8+ | Yes | Yes | \n\nNeed to include [es5-shim](https://github.com/es-shims/es5-shim#example-of-applying-es-compatability-shims-in-a-browser-project) polyfill for IE8\n\n## React Support\nCheck out \u003cb\u003ereact-infinite-tree\u003c/b\u003e at https://github.com/cheton/react-infinite-tree.\n\n## Installation\n```bash\nnpm install --save infinite-tree\n```\n\n## Usage\n```js\nconst InfiniteTree = require('infinite-tree');\n\n// when using webpack and browserify\nrequire('infinite-tree/dist/infinite-tree.css');\n\nconst data = {\n    id: 'fruit',\n    name: 'Fruit',\n    children: [{\n        id: 'apple',\n        name: 'Apple'\n    }, {\n        id: 'banana',\n        name: 'Banana',\n        children: [{\n            id: 'cherry',\n            name: 'Cherry',\n            loadOnDemand: true\n        }]\n    }]\n};\n\nconst tree = new InfiniteTree({\n    el: document.querySelector('#tree'),\n    data: data,\n    autoOpen: true, // Defaults to false\n    droppable: { // Defaults to false\n        hoverClass: 'infinite-tree-droppable-hover',\n        accept: function(event, options) {\n            return true;\n        },\n        drop: function(event, options) {\n        }\n    },\n    shouldLoadNodes: function(parentNode) {\n        if (!parentNode.hasChildren() \u0026\u0026 parentNode.loadOnDemand) {\n            return true;\n        }\n        return false;\n    },\n    loadNodes: function(parentNode, next) {\n        // Loading...\n        const nodes = [];\n        nodes.length = 1000;\n        for (let i = 0; i \u003c nodes.length; ++i) {\n            nodes[i] = {\n                id: `${parentNode.id}.${i}`,\n                name: `${parentNode.name}.${i}`,\n                loadOnDemand: true\n            };\n        }\n\n        next(null, nodes, function() {\n            // Completed\n        });\n    },\n    nodeIdAttr: 'data-id', // the node id attribute\n    rowRenderer: function(node, treeOptions) { // Customizable renderer\n        return '\u003cdiv data-id=\"\u003cnode-id\u003e\" class=\"infinite-tree-item\"\u003e' + node.name + '\u003c/div\u003e';\n    },\n    shouldSelectNode: function(node) { // Determine if the node is selectable\n        if (!node || (node === tree.getSelectedNode())) {\n            return false; // Prevent from deselecting the current node\n        }\n        return true;\n    }\n});\n```\n\n#### Functions Usage\nLearn more: [Tree](https://github.com/cheton/infinite-tree/wiki/Functions:-Tree) /  [Node](https://github.com/cheton/infinite-tree/wiki/Functions:-Node)\n```js\nconst node = tree.getNodeById('fruit');\n// → Node { id: 'fruit', ... }\ntree.selectNode(node);\n// → true\nconsole.log(node.getFirstChild());\n// → Node { id: 'apple', ... }\nconsole.log(node.getFirstChild().getNextSibling());\n// → Node { id: 'banana', ... }\nconsole.log(node.getFirstChild().getPreviousSibling());\n// → null\n```\n\n#### Events Usage\nLearn more: [Events](https://github.com/cheton/infinite-tree/wiki/Events)\n```js\ntree.on('click', function(event) {});\ntree.on('doubleClick', function(event) {});\ntree.on('keyDown', function(event) {});\ntree.on('keyUp', function(event) {});\ntree.on('clusterWillChange', function() {});\ntree.on('clusterDidChange', function() {});\ntree.on('contentWillUpdate', function() {});\ntree.on('contentDidUpdate', function() {});\ntree.on('openNode', function(Node) {});\ntree.on('closeNode', function(Node) {});\ntree.on('selectNode', function(Node) {});\ntree.on('checkNode', function(Node) {});\ntree.on('willOpenNode', function(Node) {});\ntree.on('willCloseNode', function(Node) {});\ntree.on('willSelectNode', function(Node) {});\ntree.on('willCheckNode', function(Node) {});\n```\n\n## API Documentation\n* [Options](https://github.com/cheton/infinite-tree/wiki/Options)\n* [Functions: Tree](https://github.com/cheton/infinite-tree/wiki/Functions:-Tree)\n* [Functions: Node](https://github.com/cheton/infinite-tree/wiki/Functions:-Node)\n* [Events](https://github.com/cheton/infinite-tree/wiki/Events)\n\n## FAQ\n\n### Index\n* [Creating tree nodes with checkboxes](#creating-tree-nodes-with-checkboxes)\n* [How to attach click event listeners to nodes?](#how-to-attach-click-event-listeners-to-nodes)\n* [How to use keyboard shortcuts to navigate through nodes?](#how-to-use-keyboard-shortcuts-to-navigate-through-nodes)\n* [How to filter nodes?](#how-to-filter-nodes)\n* [How to select multiple nodes using the ctrl key (or meta key)?](#how-to-select-multiple-nodes-using-the-ctrl-key-or-meta-key)\n\n#### Creating tree nodes with checkboxes\n\nSets the checked attribute in your rowRenderer:\n\n```js\nconst tag = require('html5-tag');\n\nconst checkbox = tag('input', {\n    type: 'checkbox',\n    checked: node.state.checked,\n    'class': 'checkbox',\n    'data-indeterminate': node.state.indeterminate\n});\n```\n\nIn your tree, add 'click', 'contentDidUpdate', 'clusterDidChange' event listeners as below:\n\n```js\n// `indeterminate` doesn't have a DOM attribute equivalent, so you need to update DOM on the fly.\nconst updateIndeterminateState = (tree) =\u003e {\n    const checkboxes = tree.contentElement.querySelectorAll('input[type=\"checkbox\"]');\n    for (let i = 0; i \u003c checkboxes.length; ++i) {\n        const checkbox = checkboxes[i];\n        if (checkbox.hasAttribute('data-indeterminate')) {\n            checkbox.indeterminate = true;\n        } else {\n            checkbox.indeterminate = false;\n        }\n    }\n};\n\ntree.on('click', function(node) {\n    const currentNode = tree.getNodeFromPoint(event.clientX, event.clientY);\n    if (!currentNode) {\n        return;\n    }\n\n    if (event.target.className === 'checkbox') {\n        event.stopPropagation();\n        tree.checkNode(currentNode);\n        return;\n    }\n});\n\ntree.on('contentDidUpdate', () =\u003e {\n    updateIndeterminateState(tree);\n});\n\ntree.on('clusterDidChange', () =\u003e {\n    updateIndeterminateState(tree);\n});\n```\n\n#### How to attach click event listeners to nodes?\n\nUse \u003cb\u003eevent delegation\u003c/b\u003e \u003csup\u003e[[1](http://javascript.info/tutorial/event-delegation), [2](http://davidwalsh.name/event-delegate)]\u003c/sup\u003e\n\n```js\nconst el = document.querySelector('#tree');\nconst tree = new InfiniteTree(el, { /* options */ });\n\ntree.on('click', function(event) {\n    const target = event.target || event.srcElement; // IE8\n    let nodeTarget = target;\n\n    while (nodeTarget \u0026\u0026 nodeTarget.parentElement !== tree.contentElement) {\n        nodeTarget = nodeTarget.parentElement;\n    }\n\n    // Call event.stopPropagation() if you want to prevent the execution of\n    // default tree operations like selectNode, openNode, and closeNode.\n    event.stopPropagation(); // [optional]\n    \n    // Matches the specified group of selectors.\n    const selectors = '.dropdown .btn';\n    if (nodeTarget.querySelector(selectors) !== target) {\n        return;\n    }\n\n    // do stuff with the target element.\n    console.log(target);\n};\n```\n\nEvent delegation with jQuery:\n```js\nconst el = document.querySelector('#tree');\nconst tree = new InfiniteTree(el, { /* options */ });\n\n// jQuery\n$(tree.contentElement).on('click', '.dropdown .btn', function(event) {\n    // Call event.stopPropagation() if you want to prevent the execution of\n    // default tree operations like selectNode, openNode, and closeNode.\n    event.stopPropagation();\n    \n    // do stuff with the target element.\n    console.log(event.target);\n});\n```\n\n#### How to use keyboard shortcuts to navigate through nodes?\n\n```js\ntree.on('keyDown', (event) =\u003e {\n    // Prevent the default scroll\n    event.preventDefault();\n\n    const node = tree.getSelectedNode();\n    const nodeIndex = tree.getSelectedIndex();\n\n    if (event.keyCode === 37) { // Left\n        tree.closeNode(node);\n    } else if (event.keyCode === 38) { // Up\n        if (tree.filtered) { // filtered mode\n            let prevNode = node;\n            for (let i = nodeIndex - 1; i \u003e= 0; --i) {\n                if (tree.nodes[i].state.filtered) {\n                    prevNode = tree.nodes[i];\n                    break;\n                }\n            }\n            tree.selectNode(prevNode);\n        } else {\n            const prevNode = tree.nodes[nodeIndex - 1] || node;\n            tree.selectNode(prevNode);\n        }\n    } else if (event.keyCode === 39) { // Right\n        tree.openNode(node);\n    } else if (event.keyCode === 40) { // Down\n        if (tree.filtered) { // filtered mode\n            let nextNode = node;\n            for (let i = nodeIndex + 1; i \u003c tree.nodes.length; ++i) {\n                if (tree.nodes[i].state.filtered) {\n                    nextNode = tree.nodes[i];\n                    break;\n                }\n            }\n            tree.selectNode(nextNode);\n        } else {\n            const nextNode = tree.nodes[nodeIndex + 1] || node;\n            tree.selectNode(nextNode);\n        }\n    }\n});\n```\n\n#### How to filter nodes?\n\nIn your row renderer, returns \u003ci\u003eundefined\u003c/i\u003e or an empty string to filter out unwanted nodes (i.e. `node.state.filtered === false`):\n\n```js\nimport tag from 'html5-tag';\n\nconst renderer = (node, treeOptions) =\u003e {\n    if (node.state.filtered === false) {\n        return;\n    }\n\n    // Do something\n\n    return tag('div', treeNodeAttributes, treeNode);\n};\n```\n\n##### Usage\n\n```js\ntree.filter(predicate, options)\n```\n\nUse a string or a function to test each node of the tree. Otherwise, it will render nothing after filtering (e.g. tree.filter(), tree.filter(null), tree.flter(0), tree.filter({}), etc.). If the predicate is an empty string, all nodes will be filtered. If the predicate is a function, returns \u003ci\u003etrue\u003c/i\u003e to keep the node, \u003ci\u003efalse\u003c/i\u003e otherwise.\n\n##### Filter by string\n\n```js\nconst keyword = 'text-to-filter';\nconst filterOptions = {\n    caseSensitive: false,\n    exactMatch: false,\n    filterPath: 'props.name', // Defaults to 'name'\n    includeAncestors: true,\n    includeDescendants: true\n};\ntree.filter(keyword, filterOptions);\n```\n\n##### Filter by function\n\n```js\nconst keyword = 'text-to-filter';\nconst filterOptions = {\n    includeAncestors: true,\n    includeDescendants: true\n};\ntree.filter(function(node) {\n    const name = node.name || '';\n    return name.toLowerCase().indexOf(keyword) \u003e= 0;\n});\n```\n\n##### Turn off filter\n\nCalls `tree.unfilter()` to turn off filter.\n\n```js\ntree.unfilter();\n```\n\n#### How to select multiple nodes using the ctrl key (or meta key)?\n\nYou need to maintain an array of selected nodes by yourself. See below for details:\n\n```js\nlet selectedNodes = [];\ntree.on('click', (event) =\u003e {\n    // Return the node at the specified point\n    const currentNode = tree.getNodeFromPoint(event.clientX, event.clientY);\n    if (!currentNode) {\n        return;\n    }\n\n    const multipleSelectionMode = event.ctrlKey || event.metaKey;\n\n    if (!multipleSelectionMode) {\n        if (selectedNodes.length \u003e 0) {\n            // Call event.stopPropagation() to stop event bubbling\n            event.stopPropagation();\n\n            // Empty an array of selected nodes\n            selectedNodes.forEach(selectedNode =\u003e {\n                selectedNode.state.selected = false;\n                tree.updateNode(selectedNode, {}, { shallowRendering: true });\n            });\n            selectedNodes = [];\n\n            // Select current node\n            tree.state.selectedNode = currentNode;\n            currentNode.state.selected = true;\n            tree.updateNode(currentNode, {}, { shallowRendering: true });\n        }\n        return;\n    }\n\n    // Call event.stopPropagation() to stop event bubbling\n    event.stopPropagation();\n\n    const selectedNode = tree.getSelectedNode();\n    if (selectedNodes.length === 0 \u0026\u0026 selectedNode) {\n        selectedNodes.push(selectedNode);\n        tree.state.selectedNode = null;\n    }\n\n    const index = selectedNodes.indexOf(currentNode);\n\n    // Remove current node if the array length of selected nodes is greater than 1\n    if (index \u003e= 0 \u0026\u0026 selectedNodes.length \u003e 1) {\n        currentNode.state.selected = false;\n        selectedNodes.splice(index, 1);\n        tree.updateNode(currentNode, {}, { shallowRendering: true });\n    }\n\n    // Add current node to the selected nodes\n    if (index \u003c 0) {\n        currentNode.state.selected = true;\n        selectedNodes.push(currentNode);\n        tree.updateNode(currentNode, {}, { shallowRendering: true });\n    }\n});\n```\n\n## License\n\nMIT\n","funding_links":["https://opencollective.com/cheton"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcheton%2Finfinite-tree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcheton%2Finfinite-tree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcheton%2Finfinite-tree/lists"}