Wijmo 5

TreeView 101

此页面显示如何开始使用Wijmo的TreeView控件。

入门

使用TreeView控件就像使用任何Wijmo控件:

  1. 包括所需的脚本,可以从我们的CDN的本地副本引用。详情请参阅 在您的应用程序中引用Wijmo 5。.
  2. 在托管该控件的页面上创建元素。
  3. 初始化控件,即宿主元素的id作为参数的控件,然后由可选的初始化对象。

下面的示例演示了这一切。

创建树

要创建树,通常必须设置三个属性:

  1. itemsSource 定义包含分层数据的数组。数组中的每个项包含有关节点和(可选)子节点数组的信息。
  2. displayMemberPath 定义包含要在树节点中显示的文本的项目中的属性的名称。 默认情况下,此属性设置为字符串'header'。
  3. childItemsPath 定义包含子节点数组的项目中属性的名称。 默认情况下,此属性设置为字符串'items'。

还有用于将节点图像,复选框和折叠状态绑定到itemsSource数组的属性。

默认情况下,TreeView在加载树时会展开每个级别的第一个节点。 您可以使用collapsedMemberPath 属性自定义该行为,以控制每个节点的折叠状态,或在加载树后调用 collapseToLevel方法以折叠比所需级别更深的所有节点 以显示。

加载树后,您可以使用鼠标或键盘选择,折叠或展开节点。 您还可以使用键盘搜索节点。

默认情况下,TreeView控件使用动画来展开和折叠节点。 您可以通过将isAnimated属性设置为false来关闭此功能。

它还会在节点扩展时自动折叠同级节点。 您可以通过将autoCollapse属性设置为false来关闭此功能。

默认情况下,当用户单击节点上的任意位置时,TreeView控件会展开折叠的节点。 您可以通过将 expandOnClick属性设置为false来更改此设置,在这种情况下,只有在折叠/展开的字形上的点击才会影响折叠状态。

<!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):




样式和CSS

您可以使用CSS自定义TreeView的外观。

此示例更改折叠/展开图标,根据节点级别使用不同的字体大小,并在一级节点的左侧添加垂直条。

使用TreeView下面的复选框切换自定义样式并查看区别。

<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):

导航树

TreeView控件最简单和最常见的用法是导航。TreeView的层次结构和自动搜索功能使用户可以轻松向下钻取并找到他们感兴趣的项目。

您可以使用selectedItemChangeditemClicked事件进行导航。区别在于,当用户使用键盘移动选择时,会发生selectedItemChanged当用户单击项目或按Enter键时,会发生itemClicked

这个演示使用itemClicked事件:

<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):

手风琴树

手风琴是多窗格面板,每次只保留一个面板。它们通常用于导航。

您可以使用TreeView控件来实现手风琴折叠。

使用CSS自定义标题显示并隐藏折叠/展开字形,并确保autoCollapse 属性设置为true(默认值),因此非活动面板会自动折叠。

<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):

复选框

showCheckboxes属性设置为true,TreeView将向每个节点添加复选框。

当显示复选框时,TreeView管理其层次结构,以便当复选框被选中或清除时,新值将自动应用于所有子节点,并反映在父节点的状态。

当选中或取消选中项目时,会引发checkedItemsChanged 事件,并且将使用当前选中的项目列表更新checkedItems属性。

<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):

    

图片

使用imageMemberPath 属性可通过在包含图像URL的数据项上指定属性的名称来将图像添加到节点。

例如,我们的一些示例items数组有一个“img”属性设置为图片网址:

<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):

禁用节点

您可以使用TreeNode的isDisabled属性禁用节点。 无法使用鼠标或键盘选择已禁用的节点。

<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):

自定义节点内容

您可以使用formatItem事件自定义TreeView节点的内容。 事件处理程序参数包括表示节点和要呈现的数据项的元素。

下面的示例使用formatItem事件在树上新项目的右侧添加“新”徽章。

<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):

延迟加载

延迟加载在处理大型分层数据源时很有用,并且希望避免在同时加载整个数据集时的延迟。

TreeView控件使得延迟加载超级容易。只需要两个步骤:

  1. 将父节点数据项中的items属性设置为空数组。
  2. 将TreeView的lazyLoadFunction属性设置为在用户展开节点时调用的函数。此函数有两个参数:父节点和回调函数在数据可用时调用。

下面的示例中的树以三个延迟加载节点开始。 展开它们时,将调用lazyLoadFunction。 该函数使用setTimeout来模拟http延迟并返回三个子节点的数据,其中一个子节点也是一个延迟加载节点。

该示例还使用一些CSS在节点图标加载时为其添加动画。

<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):

延迟加载和OData

此示例显示如何使用TreeView控件来显示来自OData源的分层数据。

示例通过加载Northwind employees表开始。加载数据时,代码会为每个员工添加一个“Orders”空数组。lazyLoadFunction用于在展开员工节点时装入订单。

订单表还为每个订单添加了一个“Order_Details”空数组。lazyLoadFunction用于在订单节点展开时加载订单详细信息。

<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):

拖放

allowDragging属性设置为true,以允许用户将节点拖动到TreeView中的新位置。

当允许拖动时,用户可以将任何节点拖动到树中的任何位置。 您可以通过处理TreeView拖放事件来自定义此行为:

下面的示例显示如何在TreeView控件上提供标准和自定义的拖放操作:

<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):



在树之间的拖放

allowDragging属性设置为true允许用户拖放节点在同一TreeView中。

要允许在不同TreeView控件之间拖放节点,必须处理dragOver事件,如果移动有效,请将cancel参数设置为false。

在下面的示例中,用户可以在两个树之间和之间拖动节点:

<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):

编辑节点

TreeView控件提供编辑支持。将isReadOnly属性设置为false,用户将能够通过按F2键编辑节点的内容。

使用displayMemberPath属性指定的属性,对节点内容所做的编辑会自动应用于itemsSource 数组中的项目。

您可以使用以下事件自定义编辑行为: nodeEditStarting,nodeEditStarted, nodeEditEnding,和nodeEditEnded

在下面的示例中,我们仅对不包含子节点的节点启用编辑。 要编辑,请选择一个节点,然后按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支持

一些语言从页面的右侧向左侧呈现内容(阿拉伯语和希伯来语是典型的例子)。HTML使用'dir'属性。在任何元素上将'dir'设置为'rtl'会导致元素的内容从右到左流动。

TreeView自动支持。 如果托管树的元素的“dir”属性设置为“rtl”,则树将呈现节点从右向左延伸。 您不必在控件上设置任何属性。

请注意,'dir'属性值是继承的,因此如果您在body标签上设置它,整个页面将从右到左进行渲染,包括树。

还要注意,CSS有一个'direction'属性,执行与'dir'元素属性相同的功能。 'dir'属性通常被认为更适合于几个原因,包括它可以在CSS规则中使用的事实。

<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, itemsSource: items });

Result (live):

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