#

# TreeView 101

This page shows how to get started with Wijmo's TreeView control.

## Getting Started

Steps for getting started with TreeView controls in AngularJS applications:

1. Add references to AngularJS, Wijmo, and Wijmo's AngularJS directives.
2. Add a component to provide data and logic.
3. Add a Wijmo Input control to the page and bind it to your data.
4. (Optional) Add some CSS to customize the input control's appearance.

The examples below demonstrate all this.

## Creating Trees

To create trees, you will normally have to set three properties:

1. itemsSource defines the array that contains the hierarchical data. Each item in the array contains information about a node and (optionally) an array of child nodes.
2. displayMemberPath defines the name of the property in the items that contains the text to be displayed in the tree nodes. By default, this property is set to the string 'header'.
3. childItemsPath defines the name of the property in the items that contains the array of child nodes. By default, this property is set to the string 'items'.

There are also properties for binding node images, checkboxes, and collapsed state to the itemsSource array.

By default, the TreeView expands the first node of each level when it loads the tree. You can customize that behavior using the collapsedMemberPath property to control the collapsed state of each node, or call the collapseToLevel method after the tree is loaded to collapse all nodes deeper than the level you want to show.

Once the tree is loaded, you can select, collapse, or expand nodes using the mouse or the keyboard. You can also use the keyboard to search for nodes.

The TreeView control uses animations to expand and collapse nodes by default. You can turn this feature off by setting the isAnimated property to false.

It also collapses sibling nodes automatically when a node is expanded. You can turn this feature off by setting the autoCollapse property to false.

By default, the TreeView control expands collapsed nodes when the user clicks anywhere on the node. You can change this by setting the expandOnClick property to false, in which case only clicks on the collapsed/expanded glyph will affect the collapsed state.

HTML
<!DOCTYPE html> <html> <head> <!-- Supporting libraries --> <!-- Polyfill(s) for older browsers --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/dist/zone.js"></script> <script src="node_modules/reflect-metadata/Reflect.js"></script> <script src="node_modules/systemjs/dist/system.src.js"></script> <!-- Configure SystemJS --> <script src="systemjs.config.js"></script> <!-- Bootstrap --> <link rel="stylesheet" type="text/css" href="bin/Devel/external/bootstrap/css/bootstrap.css" /> <!-- Wijmo --> <link href="bin/Devel/sources/styles/wijmo.css" rel="stylesheet" /> <!-- Configure and load application --> <link href="src/styles/app.css" rel="stylesheet" /> <!-- Load the root application module --> <script> System.import('./src/app'); </script> </head> <body> <!-- Add root application component --> <app-cmp></app-cmp> </body> </html> <!-- Root application component's template (app.html) --> <wj-tree-view #tv [itemsSource]="items" [displayMemberPath]="'header'" [childItemsPath]="'items'" [isAnimated]="isAnimated" [autoCollapse]="autoCollapse" [expandOnClick]="expandOnClick"> </wj-tree-view> <div> <button id="btnCollapse" class="btn btn-default" (click)="tv.collapseToLevel(0);">Collapse All</button> <button id="btnExpand" class="btn btn-default" (click)="tv.collapseToLevel(1000);">Expand All</button> </div> <br /> <label> <input id="chkIsAnimated" type="checkbox" [(ngModel)]="isAnimated" /> isAnimated </label> <br /> <label> <input id="chkAutoCollapse" type="checkbox" [(ngModel)]="autoCollapse" /> autoCollapse </label> <br /> <label> <input id="chkExpandOnClick" type="checkbox" [(ngModel)]="expandOnClick" /> expandOnClick </label>
TS
// Angular import * as wjcCore from 'wijmo/wijmo'; import * as wjcNav from 'wijmo/wijmo.nav'; import { Component, EventEmitter, Input, Inject, enableProdMode, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { WjNavModule } from 'wijmo/wijmo.angular2.nav'; import { TabsModule } from './components/AppTab'; @Component({ selector: 'app-cmp' }) export class AppCmp { items = []; isAnimated = true; autoCollapse = true; expandOnClick = true; constructor() { this.items = [ { header: 'Electronics', img: 'resources/electronics.png', items: [ { header: 'Trimmers/Shavers' }, { header: 'Tablets' }, { header: 'Phones', img: 'resources/phones.png', items: [ { header: 'Apple' }, { header: 'Motorola', newItem: true }, { header: 'Nokia' }, { header: 'Samsung' } ] }, { header: 'Speakers', newItem: true }, { header: 'Monitors' } ] }, ....]; } } @NgModule({ imports: [WjNavModule, BrowserModule, TabsModule], declarations: [AppCmp], bootstrap: [AppCmp] }) export class AppModule { } enableProdMode(); platformBrowserDynamic().bootstrapModule(AppModule);
CSS
/* default trees on this sample */ .wj-treeview { height: 350px; font-size: 120%; margin-bottom: 8px; background: white; box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23); }

#### Result (live):

isAnimated
autoCollapse
expandOnClick

## Styling and CSS

You can customize the appearance of the TreeView using CSS.

This example changes the collapse/expand icons, uses different font sizes depending on node level, and adds a vertical bar to the left of the level one nodes.

Use the checkbox below the TreeView to toggle the custom style and see the difference.

HTML
<wj-tree-view #tvCss [ngClass]="useCustomCss?'custom-tree':''" [itemsSource]="items" [displayMemberPath]="'header'" [childItemsPath]="'items'"> </wj-tree-view> <label> <input id="tvCssCheck" type="checkbox" checked="checked" [(ngModel)]="useCustomCss" /> Use custom CSS </label>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp { items = []; constructor() { this.items = [ { header: 'Electronics', img: 'resources/electronics.png', items: [ { header: 'Trimmers/Shavers' }, { header: 'Tablets' }, { header: 'Phones', img: 'resources/phones.png', items: [ { header: 'Apple' }, { header: 'Motorola', newItem: true }, { header: 'Nokia' }, { header: 'Samsung' } ] }, { header: 'Speakers', newItem: true }, { header: 'Monitors' } ]}, ...]; } }
CSS
/* custom tree styles */ .custom-tree.wj-treeview { color: #80044d; } /* level 0 and deeper nodes */ .custom-tree.wj-treeview .wj-nodelist > .wj-node { font-size: 120%; } /* level 1 and deeper nodes (larger font, vertical line along the left) */ .custom-tree.wj-treeview .wj-nodelist > .wj-nodelist > .wj-node, .custom-tree.wj-treeview .wj-nodelist > .wj-nodelist > .wj-nodelist { font-size: 110%; border-left: 4px solid rgba(128, 4, 77, 0.3); } /* level 2 and deeper nodes (smaller font, thinner border) */ .custom-tree.wj-treeview .wj-nodelist > .wj-nodelist > .wj-nodelist > .wj-node, .custom-tree.wj-treeview .wj-nodelist > .wj-nodelist > .wj-nodelist > .wj-nodelist { font-size: 100%; border-left: 2px solid rgba(128, 4, 77, 0.3); } /* expanded node glyph */ .custom-tree.wj-treeview .wj-nodelist .wj-node:before { content: "\e114"; font-family: 'Glyphicons Halflings'; top: 4px; border: none; opacity: .3; transition: all .3s cubic-bezier(.4,0,.2,1); } /* collapsed node glyph */ .custom-tree.wj-treeview .wj-nodelist .wj-node.wj-state-collapsed:before, .custom-tree.wj-treeview .wj-nodelist .wj-node.wj-state-collapsing:before { transform: rotate(-180deg); transition: all .3s cubic-bezier(.4,0,.2,1); }

#### Result (live):

Use custom CSS

## Navigation Trees

The simplest and most common use for the TreeView control is navigation. The TreeView's hierarchical structure and auto-search functionality make it easy for users to drill-down and find the items they are interested in.

You can use the selectedItemChanged or itemClicked events for navigation. The difference is that selectedItemChanged occurs when the user moves the selection with the keyboard, and itemClicked occurs when the user clicks an item or presses the Enter key.

This example uses the itemClicked event:

HTML
<wj-tree-view #tvNav [itemsSource]="items" [displayMemberPath]="'header'" [childItemsPath]="'items'" (itemClicked)="navTo(tvNav)"> </wj-tree-view> <div id="tvNavItem">{​{ navItem }​}</div>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp { items = []; navItem = ''; constructor() { this.items = [ { header: 'Electronics', img: 'resources/electronics.png', items: [ { header: 'Trimmers/Shavers' }, { header: 'Tablets' }, { header: 'Phones', img: 'resources/phones.png', items: [ { header: 'Apple' }, { header: 'Motorola', newItem: true }, { header: 'Nokia' }, { header: 'Samsung' }] }, { header: 'Speakers', newItem: true }, { header: 'Monitors' }] }, ....]; } // navigation display navTo(treeView: wjcNav.TreeView) { this.navItem = 'Navigating to *** ' + treeView.selectedItem.header + ' ***'; } }

#### Result (live):

{{navItem}}

## Accordion Trees

Accordions are multi-pane panels that keep only one panel expanded at a time. They are commonly used for navigation.

You can use the TreeView control to implement accordions.

Use CSS to customize the header display and to hide the collapse/expand glyphs, and make sure the autoCollapse property is set to true (the default), so non-active panels are automatically collapsed.

HTML
<wj-tree-view #tvAccordion class="accordion-tree" [itemsSource]="accordionItems" [isContentHtml]="true" [autoCollapse]="true"> </wj-tree-view> <div id="tvAccordionItem"> {​{ accordionItem }​}</div>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp implements AfterViewInit { items = []; accordioItem = ''; constructor() { this.items = [ { header: 'Electronics', img: 'resources/electronics.png', items: [ { header: 'Trimmers/Shavers' }, { header: 'Tablets' }, { header: 'Phones', img: 'resources/phones.png', items: [ { header: 'Apple' }, { header: 'Motorola', newItem: true }, { header: 'Nokia' }, { header: 'Samsung' }] }, { header: 'Speakers', newItem: true }, { header: 'Monitors' }] }, ....]; } ngAfterViewInit() { this.tvAccordion.hostElement.addEventListener('click', (e:any) => { if (e.target.tagName == 'A') { this.accordionItem = 'Navigating to *** ' + e.target.href + ' ***'; e.preventDefault(); } }); } }
CSS
/* accordion tree styles */ .accordion-tree.wj-treeview { background: transparent; box-shadow: none; height: auto; } /* hide collapse/expand glyphs */ .accordion-tree.wj-treeview .wj-nodelist .wj-node:before { display: none; } /* level 0 nodes (headers) */ .accordion-tree.wj-treeview .wj-nodelist > .wj-node { font-size: 120%; font-weight: bold; padding: 6px 10px; color: white; background: #106cc8; margin-bottom: 4px; box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24); } /* level 1 nodes (navigation items) */ .accordion-tree.wj-treeview .wj-nodelist > .wj-nodelist > .wj-node { font-size: inherit; font-weight: normal; padding: 4px 1em; color: inherit; background: inherit; box-shadow: none; } .accordion-tree.wj-treeview .wj-nodelist { padding-bottom: 6px; }

#### Result (live):

{{accordioItem}}

## Checkboxes

Set the showCheckboxes property to true and the TreeView will add checkboxes to each node.

When checkboxes are displayed, the TreeView manages their hierarchy so that when a checkbox is checked or cleared, the new value is automatically applied to all child nodes, and reflected on the state of the parent nodes.

When items are checked or unchecked, the checkedItemsChanged event is raised, and the checkedItems property is updated with a list of the items that are currently checked.

HTML
<wj-tree-view #tvChk [itemsSource]="items" [displayMemberPath]="'header'" [childItemsPath]="'items'" [showCheckboxes]="true" (checkedItemsChanged)="checkedItems(tvChk)"> </wj-tree-view> <button id="btnCheckAll" class="btn btn-default" (click)="tvChk.checkAllItems(true);"> Check All </button> <button id="btnUncheckAll" class="btn btn-default" (click)="tvChk.checkAllItems(false);"> Uncheck All </button> <button id="btnSaveState" class="btn btn-default" (click)="saveCheckedItems(tvChk)"> Save State </button> <button id="btnRestoreState" class="btn btn-default" (click)="restoreCheckedItems(tvChk)"> Restore State </button> <br /> <div id="tvChkStatus" [innerHTML]="chkStatus"> </div>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp { items = []; chkStatus = ''; private _saveCheckedItems: any; constructor() { this.items = [ { header: 'Electronics', img: 'resources/electronics.png', items: [ { header: 'Trimmers/Shavers' }, { header: 'Tablets' }, { header: 'Phones', img: 'resources/phones.png', items: [ { header: 'Apple' }, { header: 'Motorola', newItem: true }, { header: 'Nokia' }, { header: 'Samsung' }] }, { header: 'Speakers', newItem: true }, { header: 'Monitors' }] }, ....]; } // display checked items checkedItems(treeView: wjcNav.TreeView) { var items = treeView.checkedItems, msg = ''; if (items.length) { msg = '<p><b>Checked Items:</b></p><ol>\r\n'; for (var i = 0; i < items.length; i++) { msg += '<li>' + items[i].header + '</li>\r\n'; } msg += '</ol>'; } this.chkStatus = msg; } // save checked items saveCheckedItems(treeView: wjcNav.TreeView) { this._saveCheckedItems = treeView.checkedItems; } // restore checked items restoreCheckedItems(treeView: wjcNav.TreeView) { treeView.checkedItems = this._saveCheckedItems || []; } }

#### Result (live):

## Images

Use the imageMemberPath property to add images to nodes by specifying the name of a property on the data items that contains an image URL.

For example, some our sample items array have an "img" property set to image URLs:

HTML
<wj-tree-view #tvImg [itemsSource]="items" [displayMemberPath]="'header'" [childItemsPath]="'items'" [imageMemberPath]="'img'"> </wj-tree-view>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp { items = []; constructor() { this.items = [ { header: 'Electronics', img: 'resources/electronics.png', items: [ { header: 'Trimmers/Shavers' }, { header: 'Tablets' }, { header: 'Phones', img: 'resources/phones.png', items: [ { header: 'Apple' }, { header: 'Motorola', newItem: true }, { header: 'Nokia' }, { header: 'Samsung' }] }, { header: 'Speakers', newItem: true }, { header: 'Monitors' }] }, ....]; } }

#### Result (live):

## Disabled Nodes

You can disable nodes using the TreeNode's isDisabled property. Disabled nodes cannot be selected using the mouse or keyboard.

HTML
<wj-tree-view #tvDisable [itemsSource]="items" [displayMemberPath]="'header'" [childItemsPath]="'items'"> </wj-tree-view> <button id="btnDisableNode" class="btn btn-default" (click)="disableNode(tvDisable)">Disable Selected Node</button> <button id="btnEnableAllNodes" class="btn btn-default" (click)="enableAllNodes(tvDisable)">Enable All Nodes</button>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp { items = []; constructor() { this.items = [ { header: 'Electronics', img: 'resources/electronics.png', items: [ { header: 'Trimmers/Shavers' }, { header: 'Tablets' }, { header: 'Phones', img: 'resources/phones.png', items: [ { header: 'Apple' }, { header: 'Motorola', newItem: true }, { header: 'Nokia' }, { header: 'Samsung' } ] }, { header: 'Speakers', newItem: true }, { header: 'Monitors' } ] }, ....]; } // disable node disableNode(treeView: wjcNav.TreeView) { var nd = treeView.selectedNode; if (nd) { nd.isDisabled = true; } } // enable all nodes enableAllNodes(treeView: wjcNav.TreeView) { for (var nd = treeView.getFirstNode(); nd; nd = nd.next()) { nd.isDisabled = false; } } }

#### Result (live):

## Custom Node Content

You can customize the content of the TreeView nodes using the formatItem event. The event handler parameters include the element that represents the node and the data item being rendered.

The example below uses the formatItem event to add a "new" badge to the right of new items on the tree.

HTML
<wj-tree-view #tvFmtItem [itemsSource]="items" [displayMemberPath]="'header'" [childItemsPath]="'items'" (formatItem)="formatItem(tvFmtItem, $event)"> </wj-tree-view>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp { items = []; constructor() { this.items = [ { header: 'Electronics', img: 'resources/electronics.png', items: [ { header: 'Trimmers/Shavers' }, { header: 'Tablets' }, { header: 'Phones', img: 'resources/phones.png', items: [ { header: 'Apple' }, { header: 'Motorola', newItem: true }, { header: 'Nokia' }, { header: 'Samsung' }] }, { header: 'Speakers', newItem: true }, { header: 'Monitors' }] }, ....]; } // custom item formatItem(treeView: wjcNav.TreeView, e: wjcNav.FormatNodeEventArgs) { if (e.dataItem.newItem) { e.element.innerHTML += ' <img style="margin-left:6px" src="resources/new.png"/>'; } } }

#### Result (live):

## Lazy Loading

Lazy loading is useful when you are dealing with large hierarchical data sources and would like to avoid the delays involved in loading the entire data set at once.

The TreeView control makes lazy-loading super easy. Only two steps are required:

1. Set the items property in the parent node data item to an empty array.
2. Set the TreeView's lazyLoadFunction property to a function to be called when the user expands the node. This function takes two parameters: the parent node and a callback function to be invoked when the data becomes available.

The tree in example below starts with three lazy-loaded nodes. When you expand them, the lazyLoadFunction is invoked. The function uses a setTimeout to simulate an http delay and returns data for three child nodes, one of which is also a lazy-loaded node.

The example also uses some CSS to animate the node icons while they are being loaded.

HTML
<wj-tree-view #tvLazyLoad [itemsSource]="lazyItems" [displayMemberPath]="'header'" [childItemsPath]="'items'" [lazyLoadFunction]="lazyLoadFunction"> </wj-tree-view>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp { items = []; lazyLoadFunction: Function; constructor() { this.lazyItems = [ // start with three lazy-loaded nodes { header: 'Lazy Node 1', items: [] }, { header: 'Lazy Node 2', items: [] }, { header: 'Lazy Node 3', items: [] }]; // lazy loading function this.lazyLoadFunction = this._lazyLoadFunction.bind(this); } private _lazyLoadFunction(node: wjcNav.TreeNode, callback: Function) { setTimeout(function () { // simulate http delay var result = [ // simulate result { header: 'Another lazy node...', items: [] }, { header: 'A non-lazy node without children' }, { header: 'A non-lazy node with child nodes', items: [ { header: 'hello' }, { header: 'world' } ] } ]; callback(result); // return result to control }, 2500); // 2.5sec http delay } }

#### Result (live):

## Lazy Loading and OData

This example shows how you can use the TreeView control to display hierarchical data from OData sources.

The sample starts by loading the Northwind employees table. When the data is loaded, the code adds an "Orders" empty array to each employee. The lazyLoadFunction is used to load the orders when an employee node is expanded.

The orders table also adds an "Order_Details" empty array to each order. The lazyLoadFunction is used to load the order details when an order node is expanded.

HTML
<wj-tree-view #tvLazyLoadOData [itemsSource]="lazyLoadODataItems" [displayMemberPath]="['FullName', 'ShipName', 'Summary' ]" [childItemsPath]="['Orders', 'Order_Details']" [lazyLoadFunction]="lazyLoadODataFunction"> </wj-tree-view>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp { items = []; lazyLoadODataFunction: Function; constructor() { var employees; employees = new wjcData.ODataCollectionView(this._nwindService, 'Employees', { fields: 'EmployeeID,FirstName,LastName'.split(','), loaded: ()=> { var items = employees.items.map((e)=> { e.FullName = e.FirstName + ' ' + e.LastName; e.Orders = []; // lazy-load orders return e; }); this.lazyLoadODataItems = items; } }); // lazy loading function this.lazyLoadODataFunction = this._lazyLoadODataFunction.bind(this); } private _lazyLoadODataFunction(node: wjcNav.TreeNode, callback: Function) { switch (node.level) { // load orders for an employee case 0: var url = 'Employees(' + node.dataItem.EmployeeID + ')/Orders'; var orders = new wjcData.ODataCollectionView(this._nwindService, url,{ fields: 'OrderID,ShipName,ShipCountry'.split(','), loaded: ()=> { var items = orders.items.map(function (e) { e.Order_Details = []; // lazy-order details return e; }); callback(items); } }); break; // load extended details for an order case 1: var url = "Order_Details_Extendeds/?$filter=OrderID eq " + node.dataItem.OrderID; var details = new wjcData.ODataCollectionView(this._nwindService, url, { fields: 'ProductName,ExtendedPrice'.split(','), loaded: () => { var items = details.items.map( (e) =>{ e.Summary = wjcCore.format('{ProductName}: {ExtendedPrice:c}', e); return e; }); callback(items); } }); break; // default default: callback(null); } } }
CSS
/* level 0 nodes and deeper (employees...) */ #tvLazyLoadOData.wj-treeview .wj-nodelist > .wj-node { font-weight: bold; } /* level 1 nodes and deeper (orders...) */ #tvLazyLoadOData.wj-treeview .wj-nodelist > .wj-nodelist > .wj-node { font-weight: normal; font-size: 95%; color: darkblue; } /* level 2 nodes and deeper (order details...) */ #tvLazyLoadOData.wj-treeview .wj-nodelist > .wj-nodelist > .wj-nodelist > .wj-node { font-size: 90%; color: darkslategrey; }

#### Result (live):

## Drag and Drop

Set the allowDrag property to true to allow users to drag nodes to new positions within the TreeView.

When dragging is allowed, users may drag any node to any position within the tree. You can customize this behavior by handling the TreeView drag/drop events:

- dragStart: Occurs when a drag/drop operation is about to start. You may examine the node about to be dragged and cancel the operation by setting the event's cancel parameter to true.
- dragOver: Occurs while the user drags the node over other nodes on the tree. You may examine the current target node and drop position and prevent the drop or modify its location setting the event's cancel and position parameters.
- drop: Occurs when the user drops the node into its new location. You may examine the current target node and drop position and prevent the drop or modify its location setting the event's cancel and position parameters.
- dragEnd: Occurs after the drag/drop operation is finished, even if it was canceled and the source node was not moved.

The example below shows how to provide standard and customized drag/drop operations on a TreeView control:

HTML
<wj-tree-view #tvDragDrop [itemsSource]="items" [displayMemberPath]="'header'" [childItemsPath]="'items'" [imageMemberPath]="'img'" [showCheckboxes]="true" [allowDragging]="allowDragging" (dragStart)="dragStart(tvDragDrop, $event)" (dragOver)="dragOver(tvDragDrop, $event)"> </wj-tree-view> <label> <input id="allowDragging" type="checkbox" checked="checked" [(ngModel)]="allowDragging"> allowDragging </label> <br /> <label> <input id="allowDraggingParentNodes" type="checkbox" checked="checked" [(ngModel)]="allowDraggingParentNodes"> allow dragging parent nodes </label> <br /> <label> <input id="allowDroppingIntoEmpty" type="checkbox" checked="checked" [(ngModel)]="allowDroppingIntoEmpty"> allow dropping into empty nodes </label>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp { items = []; allowDraggingParentNodes = true; allowDroppingIntoEmpty = true; allowDragging = true; constructor() { this.items = [ { header: 'Electronics', img: 'resources/electronics.png', items: [ { header: 'Trimmers/Shavers' }, { header: 'Tablets' }, { header: 'Phones', img: 'resources/phones.png', items: [ { header: 'Apple' }, { header: 'Motorola', newItem: true }, { header: 'Nokia' }, { header: 'Samsung' }] }, { header: 'Speakers', newItem: true }, { header: 'Monitors' } ] }, ....]; } // use dragStart event to honor the allowDraggingParentNodes setting // by setting the 'cancel' event parameter to true dragStart(sender, e: wjcNav.TreeNodeEventArgs) { if (e.node.hasChildren) { if (!this.allowDraggingParentNodes) { e.cancel = true; // prevent dragging parent nodes } else { e.node.isCollapsed = true; // collapse parent nodes when dragging } } } // use dragOver event to honor the allowDroppingIntoEmpty setting // by changing the 'position' event parameter to 'Before' dragOver(sender, e: wjcNav.TreeNodeDragDropEventArgs) { if (!this.allowDroppingIntoEmpty && !e.dropTarget.hasChildren && e.position == wjcNav.DropPosition.Into) { e.position = wjcNav.DropPosition.Before; } } }

#### Result (live):

allowDragging
allow dragging parent nodes
allow dropping into empty nodes

## Drag and Drop Between Trees

Setting the allowDrag property to true allows users to drag and drop nodes within the same TreeView.

To allow dragging and dropping nodes between different TreeView controls, you must handle the dragOver event and set the cancel parameter to false if the move is valid.

In the example below, users can drag nodes within and between the two trees:

HTML
<wj-tree-view #tvDragDrop1 class="short" [itemsSource]="dragItems1" [displayMemberPath]="'header'" [childItemsPath]="'items'" [allowDragging]="allowDragging" (dragOver)="dragOverBetweenTrees(tvDragDrop1,tvDragDrop2, $event)"> </wj-tree-view> <wj-tree-view #tvDragDrop2 class="short" [itemsSource]="dragItems2" [displayMemberPath]="'header'" [childItemsPath]="'items'" [allowDragging]="allowDragging" (dragOver)="dragOverBetweenTrees(tvDragDrop1,tvDragDrop2, $event)"> </wj-tree-view>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp { dragItems1 = []; dragItems2 = []; constructor() { this.dragItems1 = [ { header: 'Item 1.1' }, { header: 'Item 1.2' }, { header: 'Item 1.3' } ]; this.dragItems2 = [ { header: 'Item 2.1' }, { header: 'Item 2.2' }, { header: 'Item 2.3' } ]; } // allow drag/drop between tvDragDrop1 and tvDragDrop2 dragOverBetweenTrees(tvDragDrop1: wjcNav.TreeView, tvDragDrop2: wjcNav.TreeView, e: wjcNav.TreeNodeDragDropEventArgs) { var t1 = e.dragSource.treeView; var t2 = e.dropTarget.treeView; if (t1 == tvDragDrop1 || t1 == tvDragDrop2) { if (t2 == tvDragDrop1 || t2 == tvDragDrop2) { e.cancel = false; } } } }

#### Result (live):

## Editing Nodes

The TreeView control provides editing support. Set the isReadOnly property to false and users will be able to edit the content of the nodes by pressing the F2 key.

Edits made to node contents are automatically applied to the items in the itemsSource array using the properties specified by the displayMemberPath property.

You may customize the editing behavior using the following events: nodeEditStarting, nodeEditStarted, nodeEditEnding, and nodeEditEnded.

In the example below, we enable editing only for nodes that contain no children. To edit, select a node and press F2:

HTML
<wj-tree-view #tvEdit [itemsSource]="editableItems" [displayMemberPath]="'header'" [childItemsPath]="'items'" [imageMemberPath]="'img'" [showCheckboxes]="true" [isReadOnly]="false" (nodeEditStarting)="nodeEditStarting(tvEdit, $event)"> </wj-tree-view>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp { items = []; allowDraggingParentNodes = true; allowDroppingIntoEmpty = true; allowDragging = true; constructor() { this.items = [ { header: 'Electronics', img: 'resources/electronics.png', items: [ { header: 'Trimmers/Shavers' }, { header: 'Tablets' }, { header: 'Phones', img: 'resources/phones.png', items: [ { header: 'Apple' }, { header: 'Motorola', newItem: true }, { header: 'Nokia' }, { header: 'Samsung' }] }, { header: 'Speakers', newItem: true }, { header: 'Monitors' }] }, ....]; } // editing support nodeEditStarting(tvEdit: wjcNav.TreeView, e: wjcNav.TreeNodeEventArgs) { if (e.node.hasChildren) { e.cancel = true; } } }

#### Result (live):

## RTL support

Some languages render content from the right to the left of the page (Arabic and Hebrew are typical examples) . HTML accommodates this with the 'dir' attribute. Setting 'dir' to 'rtl' on any element causes the element's content to flow from right to left.

The TreeView supports this automatically. If the element hosting the tree has the 'dir' attribute set to 'rtl', the tree will render with nodes extending from right to left. You don't have to set any properties on the control.

Note that the 'dir' attribute value is inherited, so if you set it on the body tag for example, the entire page will be rendered from right to left, including the tree.

Note also that CSS has a 'direction' attribute that performs the same function as the 'dir' element attribute. The 'dir' attribute is generally considered more appropriate for several reasons, including the fact that it can be used in CSS rules.

HTML
<div dir="rtl"> <p>My parent element has a <b>dir="rtl"</b> attribute!</p> <wj-tree-view #tvRtl [itemsSource]="items" [displayMemberPath]="'header'" [childItemsPath]="'items'" [imageMemberPath]="'img'" [showCheckboxes]="true"> </wj-tree-view> </div>
TS
@Component({ selector: 'app-cmp' }) export class AppCmp { items = []; constructor() { this.items = [ { header: 'Electronics', img: 'resources/electronics.png', items: [ { header: 'Trimmers/Shavers' }, { header: 'Tablets' }, { header: 'Phones', img: 'resources/phones.png', items: [ { header: 'Apple' }, { header: 'Motorola', newItem: true }, { header: 'Nokia' }, { header: 'Samsung' } ] }, { header: 'Speakers', newItem: true }, { header: 'Monitors' }] }, ....]; } }

#### Result (live):

My parent element has a dir="rtl" attribute!