Wijmo 5

TreeView 101

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

Getting Started

Using the TreeView control is like using any Wijmo control:

  1. Include the required scripts, either from local copies of from our CDN. For details, please see Referencing Wijmo 5 in Your Applications.
  2. Create elements on the page that will host the controls.
  3. Initialize the controls passing the id of the host element as a parameter, followed by an optional initialization object.

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.

<!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="css/bootstrap.css"/> <link rel="stylesheet" type="text/css" href="css/wijmo.css" /> <script src="scripts/wijmo.js" type="text/javascript"></script> <script src="scripts/wijmo.input.js" type="text/javascript"></script> <link href="css/app.css" rel="stylesheet" type="text/css" /> <script src="scripts/app.js" type="text/javascript"></script> </head> <body> <div id="tv"></div> <button id="btnCollapse" class="btn btn-default">Collapse All</button> <button id="btnExpand" class="btn btn-default">Expand All</button> <br/> <label> <input id="chkIsAnimated" type="checkbox" checked="checked"> isAnimated </label> <br/> <label> <input id="chkAutoCollapse" type="checkbox" checked="checked"> autoCollapse </label> <br/> <label> <input id="chkexpandOnClick" type="checkbox" checked="checked"> expandOnClick </label> </body> </html>
onload = function () { // TreeView data var items = [ { header: 'Electronics', img: 'resources/electronics.png', items: [ { header: 'Trimmers/Shavers' }, { header: 'Tablets' }, { header: 'Phones', img: 'resources/phones.png', items: [ { header: 'Apple' }, { header: 'Motorola' }, { header: 'Nokia' }, { header: 'Samsung' } ]}, { header: 'Speakers' }, { header: 'Monitors' } ]}, { header: 'Toys', img: 'resources/toys.png', items: [ { header: 'Shopkins' }, ... ]}, { header: 'Home', img: 'resources/home.png', items: [ { header: 'Coffeee Maker' }, ... ]} ]; // create and bind the TreeView var tv = new wijmo.nav.TreeView('#tv', { displayMemberPath: 'header', childItemsPath: 'items', itemsSource: items }); // handle collapse/expand buttons document.getElementById('btnCollapse').addEventListener('click', function () { tv.collapseToLevel(0); }); document.getElementById('btnExpand').addEventListener('click', function () { tv.collapseToLevel(1000); }); // handle checkboxes document.getElementById('chkAutoCollapse').addEventListener('change', function (e) { tv.autoCollapse = e.target.checked; }); document.getElementById('chkIsAnimated').addEventListener('change', function (e) { tv.isAnimated = e.target.checked; }); document.getElementById('chkExpandOnClick').addEventListener('change', function (e) { tv.expandOnClick = e.target.checked; }); }
/* 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):




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.

<div id="tvCss" class="custom-tree"></div> <label> Use custom CSS <input id="tvCssCheck" type="checkbox" checked="checked"/> </label>
// create and bind the 'CSS' TreeView var tvCss = new wijmo.nav.TreeView('#tvCss', { displayMemberPath: 'header', childItemsPath: 'items', itemsSource: items }); // toggle style when user checks the checkbox document.getElementById('tvCssCheck').addEventListener('change', function(e) { wijmo.toggleClass(tvCss.hostElement, 'custom-tree', e.target.checked); });
/* 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):

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:

<div id="tvNav"></div> <div id="tvNavItem"></div>
// create and bind the 'Navigation' TreeView var tvNav = new wijmo.nav.TreeView('#tvNav', { displayMemberPath: 'header', childItemsPath: 'items', itemsSource: items, itemClicked: function (s, e) { document.getElementById('tvNavItem').innerHTML = 'Navigating to *** ' + s.selectedItem.header + ' ***'; } });

Result (live):

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.

<div id="tvAccordion" class="accordion-tree"></div> <div id="tvAccordionItem"></div>
// create and bind the 'Accordion' TreeView var tvAccordion = new wijmo.nav.TreeView('#tvAccordion', { isContentHtml: true, autoCollapse: true, itemsSource: [ { header: 'Angular', items: [ { header: '<a href="ng/intro">Introduction</a>' }, { header: '<a href="ng/samples">Samples</a>' }, { header: '<a href="ng/perf">Performance</a>' } ]}, ... ] }); /* handle clicks on accordion items */ tvAccordion.hostElement.addEventListener('click', function (e) { if (e.target.tagName == 'A') { document.getElementById('tvAccordionItem').innerHTML = 'Navigating to <b>*** ' + e.target.href + ' ***</b>'; e.preventDefault(); } });
/* 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):

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.

<div id="tvChk"></div> <button id="btnCheckAll" class="btn btn-default" > Check All </button> <button id="btnUncheckAll" class="btn btn-default"> Uncheck All </button>      <button id="btnSaveState" class="btn btn-default" > Save State </button> <button id="btnRestoreState" class="btn btn-default"> Restore State </button> <br/> <div id="tvChkStatus"></div>
// create and bind the 'Checkboxes' TreeView var tvChk = new wijmo.nav.TreeView('#tvChk', { displayMemberPath: 'header', childItemsPath: 'items', showCheckboxes: true, itemsSource: items, checkedItemsChanged: function (s, e) { var items = s.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>'; } document.getElementById('tvChkStatus').innerHTML = msg; } }); // check/uncheck all nodes document.getElementById('btnCheckAll').addEventListener('click', function () { tvChk.checkAllItems(true); }); document.getElementById('btnUncheckAll').addEventListener('click', function () { tvChk.checkAllItems(false); }); // save/restore checked state var saveCheckedItems = null; document.getElementById('btnSaveState').addEventListener('click', function () { saveCheckedItems = tvChk.checkedItems; }); document.getElementById('btnRestoreState').addEventListener('click', function () { tvChk.checkedItems = 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:

<div id="tvImg"></div>
// create and bind the 'Images' TreeView var tvImg = new wijmo.nav.TreeView('#tvImg', { displayMemberPath: 'header', imageMemberPath: 'img', childItemsPath: 'items', itemsSource: items });

Result (live):

Disabled Nodes

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

<div id="tvDisable"></div> <button id="btnDisableNode" class="btn btn-default">Disable Selected Node</button> <button id="btnEnableAllNodes" class="btn btn-default">Enable All Nodes</button>
// create and bind the 'Disable Items' TreeView var tvDisable = new wijmo.nav.TreeView('#tvDisable', { displayMemberPath: 'header', childItemsPath: 'items', itemsSource: items, }); // disable selected node document.getElementById('btnDisableNode').addEventListener('click', function () { var nd = tvDisable.selectedNode; if (nd) { nd.isDisabled = true; } }); // enable all nodes document.getElementById('btnEnableAllNodes').addEventListener('click', function () { for (var nd = tvDisable.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.

<div id="tvFmtItem"></div>
// create and bind the 'Custom Content' TreeView var tvFmtItem = new wijmo.nav.TreeView('#tvFmtItem', { displayMemberPath: 'header', childItemsPath: 'items', itemsSource: items, formatItem: function (s, e) { 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.

<div id="tvLazyLoad"></div>
// create and bind the 'Lazy Load' TreeView var tvLazyLoad = new wijmo.nav.TreeView('#tvLazyLoad', { displayMemberPath: 'header', childItemsPath: 'items', itemsSource: [ // start with three lazy-loaded nodes { header: 'Lazy Node 1', items: []}, { header: 'Lazy Node 2', items: [] }, { header: 'Lazy Node 3', items: [] } ], lazyLoadFunction: function (node, callback) { 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.

<div id="tvLazyLoadOData"></div>
// demonstrate lazy-loading with OData var nwindService = 'http://services.odata.org/V4/Northwind/Northwind.svc'; var tvLazyLoadOData = new wijmo.nav.TreeView('#tvLazyLoadOData', { displayMemberPath: ['FullName', 'ShipName', 'Summary' ], childItemsPath: ['Orders', 'Order_Details'], lazyLoadFunction: function (node, callback) { switch (node.level) { // load orders for an employee case 0: var url = 'Employees(' + node.dataItem.EmployeeID + ')/Orders'; var orders = new wijmo.odata.ODataCollectionView(nwindService, url, { fields: 'OrderID,ShipName,ShipCountry'.split(','), loaded: function () { 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 wijmo.odata.ODataCollectionView(nwindService, url, { fields: 'ProductName,ExtendedPrice'.split(','), loaded: function () { var items = details.items.map(function (e) { e.Summary = wijmo.format('{ProductName}: {ExtendedPrice:c}', e); return e; }); callback(items); } }); break; // default default: callback(null); } } }); // first level: employees var employees = new wijmo.odata.ODataCollectionView(nwindService, 'Employees', { fields: 'EmployeeID,FirstName,LastName'.split(','), loaded: function () { var items = employees.items.map(function (e) { e.FullName = e.FirstName + ' ' + e.LastName; e.Orders = []; // lazy-load orders return e; }); tvLazyLoadOData.itemsSource = items; } });
/* 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 allowDragging 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:

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

<div id="tvDragDrop"></div> <label> <input id="allowDragging" type="checkbox" checked="checked"> allowDragging </label> <br/> <label> <input id="allowDraggingParentNodes" type="checkbox" checked="checked"> allow dragging parent nodes </label> <br/> <label> <input id="allowDroppingIntoEmpty" type="checkbox" checked="checked"> allow dropping into empty nodes </label>
// create and bind the drag/drop TreeView var allowDraggingParentNodes = true, allowDroppingIntoEmpty = true; var tvDragDrop = new wijmo.nav.TreeView('#tvDragDrop', { displayMemberPath: 'header', childItemsPath: 'items', imageMemberPath: 'img', showCheckboxes: true, allowDragging: true, itemsSource: items, // use dragStart event to honor the allowDraggingParentNodes setting // by setting the 'cancel' event parameter to true dragStart: function (s, e) { if (e.node.hasChildren) { if (!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: function (s, e) { if (!allowDroppingIntoEmpty && !e.dropTarget.hasChildren && e.position == wijmo.input.DropPosition.Into) { e.position = wijmo.input.DropPosition.Before; } }, });

Result (live):



Drag and Drop Between Trees

Setting the allowDragging 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:

<div class="short" id="tvDragDrop1"></div> <div class="short" id="tvDragDrop2"></div>
// create trees to drag/drop between var tvDragDrop1 = new wijmo.nav.TreeView('#tvDragDrop1', { displayMemberPath: 'header', childItemsPath: 'items', allowDragging: true, dragOver: dragOverBetweenTrees, itemsSource: [ { header: 'Item 1.1' }, { header: 'Item 1.2' }, { header: 'Item 1.3' }, ] }); var tvDragDrop2 = new wijmo.nav.TreeView('#tvDragDrop2', { displayMemberPath: 'header', childItemsPath: 'items', allowDragging: true, dragOver: dragOverBetweenTrees, itemsSource: [ { header: 'Item 2.1' }, { header: 'Item 2.2' }, { header: 'Item 2.3' }, ] }); // allow drag/drop between tvDragDrop1 and tvDragDrop2 function dragOverBetweenTrees(s, e) { var t1 = e.dragSource.treeView, 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:

<div id="tvEdit"></div>
// create and bind the 'Editable Nodes' TreeView var tvEdit = new wijmo.nav.TreeView('#tvEdit', { displayMemberPath: 'header', childItemsPath: 'items', imageMemberPath: 'img', showCheckboxes: true, itemsSource: items, isReadOnly: false, nodeEditStarting: function (s, e) { 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.

<div dir="rtl"> <p>My parent element has a <b>dir="rtl"</b> attribute!</p> <div id="tvRtl"></div> </div>
// demonstrate RTL support (no need to set any properties) var tvRtl = new wijmo.nav.TreeView('#tvRtl', { displayMemberPath: 'header', childItemsPath: 'items', imageMemberPath: 'img', showCheckboxes: true, allowDragging: true, itemsSource: items });

Result (live):

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