Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Jstree Bundle Laravel Package

tadcka/jstree-bundle

Symfony bundle that integrates the jsTree JavaScript library, making it easy to render interactive, hierarchical tree views in your Symfony applications. Wraps jsTree configuration and assets for quick, minimal setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the bundle (Symfony 2.x only). Add the package with Composer, register it in app/AppKernel.php, and publish its assets before anything will render:

// app/AppKernel.php
new Tadcka\Bundle\JsTreeBundle\TadckaJsTreeBundle(),
composer require tadcka/js-tree-bundle
php app/console assets:install web --symlink
php app/console assetic:dump          # only if you use Assetic

Where to look first. The bundle is small; read it in this order:

  1. DependencyInjection/ and Resources/config/services.xml — tells you which services (tree builder, node factory, form type) are exposed and what parameters can be overridden.
  2. Resources/views/ — the Twig template that boots the jsTree widget; this is the contract you will most likely override.
  3. Resources/public/js|css — which jsTree version is actually bundled (v1 vs v3 matters a lot, see Gotchas).
  4. Form/ and Model/ — whether a form type or tree-node abstraction exists for binding selections to an entity.

First use case. Render a read-only tree in a template, then back it with your own controller returning JSON:

{# base layout: include jQuery and the bundle assets once #}
{% include 'TadckaJsTreeBundle::assets.html.twig' %}

<div id="tree" data-url="{{ path('app_category_tree') }}"></div>
// controller
public function treeAction()
{
    // build a nested array of {id, text, children} from your repository
    return new JsonResponse($this->categoryTree->build($this->getUser()));
}

Once that renders, add selection, then drag-and-drop persistence. Those three steps cover ~90% of real usage.

Implementation Patterns

Server-side tree vs. lazy loading. Two viable modes:

  • Eager: build the entire nested array in one action and pass it as data to the widget. Correct for small trees (< a few thousand nodes); simplest to secure because one query path guards everything.
  • Lazy: return only the root level with "children": true on nodes that have kids, and let jsTree re-hit the same endpoint with the parent id. This is what you want for large or per-user permissioned trees, but it doubles the correctness burden — every request must re-authorize the parent node.
private function toNode(Category $c, bool $withChildren = false): array
{
    return [
        'id'    => $c->getId(),
        'text'  => $c->getName(),
        'state' => ['opened' => $c->getDepth() < 1],
        'children' => $withChildren ? array_map(fn($k) => $this->toNode($k), $c->getChildren()->toArray()) : true,
    ];
}

Note the asymmetry: children: true (a literal true) means "collapsed, fetch on expand", while children: [...] means "already loaded". Getting this backwards causes either infinite expand requests or nodes that never open.

Form type integration. If the bundle ships a form type, it is almost always a wrapper around a hidden input holding selected ids, synced by JS. The pattern that survives contact with production is to keep the form field dumb (hidden, multiple) and let your controller reconcile ids to entities:

$builder->add('nodes', 'hidden'); // populated by jsTree, validated server-side

Bind a listener rather than the default handler so you control the event: $('#tree').on('changed.jstree', function (e, d) { $('#form_nodes').val(d.selected.join(',')); });

Drag-and-drop persistence. The useful event is move_node.jstree, not the drop callback. Debounce and post the single moved node plus its new parent and index; do not post the whole tree JSON unless your tree is tiny. Persist inside one transaction and re-read the sibling ordering server-side, since optimistic client order is only a hint.

Options, not template edits. Most widgets of this vintage accept an options array from Twig ({'plugins': ['checkbox','dnd'], 'core': {...}}). Prefer passing options over forking the bundle template — you stay on upstream bug fixes, of which there will be none.

Asset bundling. In an Assetic-era layout, alias the bundle assets once in base.html.twig and never inside the partial, otherwise multi-widget pages load jQuery twice and silently re-initialize the first tree.

Gotchas and Tips

It is abandoned. Last release September 2015, Symfony 2 era, essentially zero users. It will not install on Symfony 4+ without fighting templating, assets, and service-visibility changes. Before committing to it, measure the actual work: a controller returning jsTree-shaped JSON plus one Twig partial is roughly 40 lines. If the bundle only saves you that, take the 40 lines and pin a current jsTree from npm/CDN.

jsTree version mismatch is the #1 failure. The bundle's templates and JS predate jsTree 3. Node shape changed: v1 used data/metadata/attr, v3 uses text/icon/state/children. If nodes render as [object Object] or are blank, you are feeding v3 shape into v1 defaults or vice versa. Check $.jstree.version in the console before debugging anything else.

"jstree is not a function" almost always means assets:install was skipped, the assets live under a hardcoded /bundles/tadckajstree/... path, or the app is served from a subdirectory. Fix by using {{ asset(...) }} or the bundle's templated path, and clear app/cache after any asset change.

Initialization timing. Call .jstree() on document.ready and after the asset script has executed. Ordering with Assetic's combined output is nondeterministic enough that a $(function(){...}) guard is worth adding even if it looks redundant.

Debugging. Grab the instance from the console rather than hunting closures: $('#tree').jstree(true).get_json() gives you exactly what the client thinks the tree is — usually enough to separate "bad data from server" from "bad rendering". Pair it with the Network tab on the AJAX endpoint; jsTree silently swallows non-2xx and JSON parse errors, leaving you with an empty tree and no exception.

Security is server-side only. state.disabled and hidden nodes are cosmetic — anyone can POST any parent id. Authorize every node in the JSON endpoint and in the move/rename/delete handlers, and add CSRF tokens to the mutating endpoints; the bundle will not do it for you.

Caching the "has children" check. Lazy mode needs a per-node child count, which is an easy N+1. Either denormalize a hasChildren flag or use a tree repository (Gedmo nested-set getChildren, or a recursive CTE) and cache the count per node.

Extension points if you keep it. Override the Twig template by shadowing it in app/Resources/TadckaJsTreeBundle/views/, and override constructor arguments in config.yml under the bundle's service ids rather than subclassing. There is no maintained upstream to merge your fixes into, so keep any patch in your own repo and document why.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core