TreeView Accordion

下面的控件看起来像一个Accordion,但实际上是一个带有一些CSS和 formatItem 事件处理程序的TreeView。

树会自动展开所选项并折叠所有其他项,并确保只能选择顶级节点:

import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; import * as wjNav from '@grapecity/wijmo.nav'; import * as wjCore from '@grapecity/wijmo'; import { getData } from './data'; // document.readyState === 'complete' ? init() : window.onload = init; // function init() { // // create a list of items with header and detail properties var treeData = []; var data = getData(); for (var i = 0; i < data.length; i++) { treeData.push({ header: data[i].name, detail: [data[i]] }); } // // define template for the details var itemTemplate = '<div class="item">' + '<b>{city}</b> ({state})<br/>' + '{address}<br/>' + 'Phone: <b>{phone}</b><br/>' + 'Fax: <b>{fax}</b><br/>' + 'Website: <a href="{site}">{site}</a><br/>' + '</div>'; // // generate an Accordion-like TreeView var theTree = new wjNav.TreeView('#treeview', { displayMemberPath: 'header', childItemsPath: 'detail', itemsSource: treeData, formatItem: function (s, e) { switch (e.level) { // // level 0: wrap header in an H1 tag case 0: e.element.innerHTML = '<h1>' + e.dataItem.header + '<h1>'; break; // // level 1: use template to create details case 1: var html = wjCore.format(itemTemplate, e.dataItem, function (data, name, fmt, val) { if (wjCore.isString(data[name])) { val = wjCore.escapeHtml(data[name]); } return val; }); e.element.innerHTML = html; break; } } }); // // expand selected items, show selection theTree.selectedItemChanged.addHandler(function (s, e) { var node = theTree.selectedNode; if (node && node.parentNode) { node = theTree.selectedNode = node.parentNode; } node.isCollapsed = false; document.getElementById('selected').textContent = node.dataItem.header; }); theTree.selectedItem = theTree.itemsSource[0]; // // handle up-arrow key to skip details theTree.hostElement.addEventListener('keydown', function (e) { var node = null; switch (e.keyCode) { case wjCore.Key.Up: node = theTree.selectedNode.previousSibling(); break; case wjCore.Key.Down: node = theTree.selectedNode.nextSibling(); break; } if (node) { theTree.selectedNode = node; e.preventDefault(); } }); } <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>GrapeCity Wijmo TreeView Styling Clean Accordion</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div class="container-fluid"> <p> Selected item: <b><span id="selected"></span></b> </p> <div id="treeview"></div> </div> </body> </html> // some data to show in our accordion export function getData() { return [{ name: 'Electro Mart', city: 'Accident', state: 'Maryland', address: '8785 Windfall St.', phone: '(800) 555-1234', fax: '(800) 555-5678', site: 'https://www.electromartnot.com' }, { name: 'Sue\'s Depot', city: 'Big Arm', state: 'Montana', address: '77 Winchester Lane', phone: '(800) 555-2345', fax: '(800) 555-6789', site: 'https://www.suesdepotnot.com' }, { name: 'D&K Digital Locker', city: 'Chicken', state: 'Alaska', address: '787 Lakeview St. ', phone: '(800) 555-3456', fax: '(800) 555-7890', site: 'https://www.digilockernot.com' }, { name: 'Paul\'s Pub & Bistro', city: 'Coupon', state: 'Pennsylvania', address: '711 Old York Drive ', phone: '(800) 555-0987', fax: '(800) 555-6543', site: 'https://www.paulspubnot.com' }, { name: 'Amazing Deals Inc', city: 'Cut And Shoot', state: 'Texas', address: '91 Beech St.', phone: '(800) 955-2109', fax: '(800) 955-8765', site: 'https://www.amazingdealsnot.com' }]; } body { font-family: 'Segoe UI'; font-size: 14px; } .item p { margin: 0; } .item h1 { margin-bottom: 0; font-size: 20px; font-weight: 200; } .wj-treeview { margin: 12px; padding: 0 12px 12px 12px; background: #fafafa; box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); } /* customize TreeView content */ .wj-treeview h1 { display: inline-block; margin-top: 10px; margin-bottom: 0; font-size: 20px; font-weight: 200; } /* hide collapse/expand icons */ .wj-treeview .wj-nodelist .wj-node:before { display: none; } /* remove selected style */ .wj-treeview .wj-node.wj-state-selected { color: inherit; background: transparent; } import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; import './styles.css'; import * as wjCore from '@grapecity/wijmo'; import * as wjNav from '@grapecity/wijmo.nav'; // import { Component, Inject, enableProdMode, NgModule, AfterViewInit, ViewChild } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; import { WjNavModule, WjTreeView } from '@grapecity/wijmo.angular2.nav'; import { DataService, TreeItem } from './app.data'; // define template for the details var itemTemplate = '<div class="item">' + '<b>{city}</b> ({state})<br/>' + '{address}<br/>' + 'Phone: <b>{phone}</b><br/>' + 'Fax: <b>{fax}</b><br/>' + 'Website: <a href="{site}">{site}</a><br/>' + '</div>'; // @Component({ selector: 'app-component', templateUrl: 'src/app.component.html' }) export class AppComponent implements AfterViewInit { @ViewChild('theTree') theTree: WjTreeView; data: any[]; clickMsg: string; selectionMsg: string; // constructor(@Inject(DataService) private dataService: DataService) { this.data = this._getTreeData(dataService.getData()); } // ngAfterViewInit() { var theTree = this.theTree; theTree.selectedItem = theTree.itemsSource[0]; // handle up-arrow key to skip details theTree.hostElement.addEventListener('keydown', function (e) { var node = null; switch (e.keyCode) { case wjCore.Key.Up: node = theTree.selectedNode.previousSibling(); break; case wjCore.Key.Down: node = theTree.selectedNode.nextSibling(); break; } if (node) { theTree.selectedNode = node; e.preventDefault(); } }); } // onFormatItem(s: WjTreeView, e: wjNav.FormatNodeEventArgs) { switch (e.level) { // // level 0: wrap header in an H1 tag case 0: e.element.innerHTML = '<h1>' + e.dataItem.header + '<h1>'; break; // // level 1: use template to create details case 1: var html = wjCore.format(itemTemplate, e.dataItem, function (data: TreeItem, name: string, fmt: Function | undefined, val: string) { if (wjCore.isString(data[name])) { val = wjCore.escapeHtml(data[name]); } return val; }); e.element.innerHTML = html; break; } } // onSelectedItemChanged() { var theTree = this.theTree; var node = theTree.selectedNode; if (node && node.parentNode) { node = theTree.selectedNode = node.parentNode; } node.isCollapsed = false; document.getElementById('selected').textContent = node.dataItem.header; } // _getTreeData(data: TreeItem[]) { var treeData = []; for (var i = 0; i < data.length; i++) { treeData.push({ header: data[i].name, detail: [data[i]] }); } return treeData; } } // @NgModule({ imports: [WjNavModule, BrowserModule], declarations: [AppComponent], providers: [DataService], bootstrap: [AppComponent] }) export class AppModule { } // enableProdMode(); // Bootstrap application with hash style navigation and global services. platformBrowserDynamic().bootstrapModule(AppModule); <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>GrapeCity Wijmo TreeView Styling Clean Accordion</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- Polyfills --> <script src="node_modules/core-js/client/shim.min.js"></script> <script src="node_modules/zone.js/dist/zone.min.js"></script> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.js"></script> <script src="systemjs.config.js"></script> <script> // workaround to load 'rxjs/operators' from the rxjs bundle System.import('rxjs').then(function (m) { System.set(SystemJS.resolveSync('rxjs/operators'), System.newModule(m.operators)); System.import('./src/app.component'); }); </script> </head> <body> <app-component></app-component> </body> </html> <div class="container-fluid"> <p> Selected item: <b><span id="selected"></span></b> </p> <wj-tree-view #theTree [itemsSource]="data" [displayMemberPath]="'header'" [childItemsPath]="'detail'" (formatItem)="onFormatItem(theTree, $event)" (selectedItemChanged)="onSelectedItemChanged()"></wj-tree-view> </div> import { Injectable } from '@angular/core'; export class TreeItem { name: string; city: string; state: string; address: string; phone: string; fax: string; site: string; } @Injectable() export class DataService { getData(): TreeItem[] { return [{ name: 'Electro Mart', city: 'Accident', state: 'Maryland', address: '8785 Windfall St.', phone: '(800) 555-1234', fax: '(800) 555-5678', site: 'https://www.electromartnot.com' }, { name: 'Sue\'s Depot', city: 'Big Arm', state: 'Montana', address: '77 Winchester Lane', phone: '(800) 555-2345', fax: '(800) 555-6789', site: 'https://www.suesdepotnot.com' }, { name: 'D&K Digital Locker', city: 'Chicken', state: 'Alaska', address: '787 Lakeview St. ', phone: '(800) 555-3456', fax: '(800) 555-7890', site: 'https://www.digilockernot.com' }, { name: 'Paul\'s Pub & Bistro', city: 'Coupon', state: 'Pennsylvania', address: '711 Old York Drive ', phone: '(800) 555-0987', fax: '(800) 555-6543', site: 'https://www.paulspubnot.com' }, { name: 'Amazing Deals Inc', city: 'Cut And Shoot', state: 'Texas', address: '91 Beech St.', phone: '(800) 955-2109', fax: '(800) 955-8765', site: 'https://www.amazingdealsnot.com' }]; } } body { font-family: 'Segoe UI'; font-size: 14px; } .item p { margin: 0; } .item h1 { margin-bottom: 0; font-size: 20px; font-weight: 200; } .wj-treeview { margin: 12px; padding: 0 12px 12px 12px; background: #fafafa; box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); } /* customize TreeView content */ .wj-treeview h1 { display: inline-block; margin-top: 10px; margin-bottom: 0; font-size: 20px; font-weight: 200; } /* hide collapse/expand icons */ .wj-treeview .wj-nodelist .wj-node:before { display: none; } /* remove selected style */ .wj-treeview .wj-node.wj-state-selected { color: inherit; background: transparent; } <template> <div class="container-fluid"> <p> Selected item: <b><span id="selected"></span></b> </p> <wj-tree-view :items-source="data" display-member-path="header" child-items-path="detail" :formatItem="onFormatItem" :selected-item-changed="onSelectedItemChanged" :initialized="initTreeView"></wj-tree-view> </div> </template> <script> import 'bootstrap.css'; import "@grapecity/wijmo.styles/wijmo.css"; import Vue from 'vue'; import '@grapecity/wijmo.vue2.nav'; import * as wjCore from '@grapecity/wijmo'; import { getData } from './data'; var wjTreeViewControl = null; var itemTemplate = '<div class="item">' + '<b>{city}</b> ({state})<br/>' + '{address}<br/>' + 'Phone: <b>{phone}</b><br/>' + 'Fax: <b>{fax}</b><br/>' + 'Website: <a href="{site}">{site}</a><br/>' + '</div>'; new Vue({ el: '#app', data: function () { return { data: _getTreeData(getData()), msg: 'Ready' } }, methods:{ initTreeView: function(ctl){ wjTreeViewControl = ctl; }, onFormatItem: function(s, e) { switch (e.level) { // // level 0: wrap header in an H1 tag case 0: e.element.innerHTML = '<h1>' + e.dataItem.header + '<h1>'; break; // // level 1: use template to create details case 1: var html = wjCore.format(itemTemplate, e.dataItem, function (data, name, fmt, val ) { if (wjCore.isString(data[name])) { val = wjCore.escapeHtml(data[name]); } return val; }); e.element.innerHTML = html; break; } }, onSelectedItemChanged: function() { var node = wjTreeViewControl.selectedNode; if (node && node.parentNode) { node = wjTreeViewControl.selectedNode = node.parentNode; } node.isCollapsed = false; document.getElementById('selected').textContent = node.dataItem.header; } }, mounted: function(e) { wjTreeViewControl.selectedItem = wjTreeViewControl.itemsSource[0]; document.getElementById('selected').textContent = wjTreeViewControl.selectedNode.dataItem.header; wjTreeViewControl.hostElement.addEventListener("keydown", function(e) { var node = null; switch (e.keyCode) { case wjCore.Key.Up: node = wjTreeViewControl.selectedNode.previousSibling(); break; case wjCore.Key.Down: node = wjTreeViewControl.selectedNode.nextSibling(); break; } if (node) { wjTreeViewControl.selectedNode = node; document.getElementById('selected').textContent = node.dataItem.header; e.preventDefault(); } }); } }) function _getSelectedNode(){ var node = wjTreeViewControl.selectedNode; if (node && node.parentNode) { node = wjTreeViewControl.selectedNode = node.parentNode; } node.isCollapsed = false; document.getElementById('selected').textContent = node.dataItem.header; } function _getTreeData(data) { var treeData = []; for (var i = 0; i < data.length; i++) { treeData.push({ header: data[i].name, detail: [data[i]] }); } return treeData; } </script> <style> body { font-family: 'Segoe UI'; font-size: 14px; } .item p { margin: 0; } .item h1 { margin-bottom: 0; font-size: 20px; font-weight: 200; } .container-fluid .wj-treeview { margin: 12px; padding: 0 12px 12px 12px; background: #fafafa; box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); } /* customize TreeView content */ .container-fluid .wj-treeview h1 { display: inline-block; margin-top: 10px; margin-bottom: 0; font-size: 20px; font-weight: 200; } /* hide collapse/expand icons */ .container-fluid .wj-treeview .wj-nodelist .wj-node:before { display: none; } /* remove selected style */ .container-fluid .wj-treeview .wj-node.wj-state-selected { color: inherit; background: transparent; } </style> <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>GrapeCity Wijmo TreeView Styling Clean Accordion</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app.vue'); </script> </head> <body> <div id="app"> </div> </body> </html> export function getData(){ return [{ name: 'Electro Mart', city: 'Accident', state: 'Maryland', address: '8785 Windfall St.', phone: '(800) 555-1234', fax: '(800) 555-5678', site: 'https://www.electromartnot.com' }, { name: 'Sue\'s Depot', city: 'Big Arm', state: 'Montana', address: '77 Winchester Lane', phone: '(800) 555-2345', fax: '(800) 555-6789', site: 'https://www.suesdepotnot.com' }, { name: 'D&K Digital Locker', city: 'Chicken', state: 'Alaska', address: '787 Lakeview St. ', phone: '(800) 555-3456', fax: '(800) 555-7890', site: 'https://www.digilockernot.com' }, { name: 'Paul\'s Pub & Bistro', city: 'Coupon', state: 'Pennsylvania', address: '711 Old York Drive ', phone: '(800) 555-0987', fax: '(800) 555-6543', site: 'https://www.paulspubnot.com' }, { name: 'Amazing Deals Inc', city: 'Cut And Shoot', state: 'Texas', address: '91 Beech St.', phone: '(800) 955-2109', fax: '(800) 955-8765', site: 'https://www.amazingdealsnot.com' }]; } import './app.css'; import 'bootstrap.css'; import '@grapecity/wijmo.styles/wijmo.css'; // import * as React from 'react'; import * as ReactDOM from 'react-dom'; // import * as wjNav from '@grapecity/wijmo.react.nav'; import * as wjCore from '@grapecity/wijmo'; import { getData } from './data'; class App extends React.Component { constructor(props) { super(props); this._wjTreeViewControl = null; this._itemTemplate = '<div class="item">' + '<b>{city}</b> ({state})<br/>' + '{address}<br/>' + 'Phone: <b>{phone}</b><br/>' + 'Fax: <b>{fax}</b><br/>' + 'Website: <a href="{site}">{site}</a><br/>' + '</div>'; this.state = this.getTreeData(getData()); } initTreeView(ctl) { this._wjTreeViewControl = ctl; } onFormatItem(s, e) { switch (e.level) { // // level 0: wrap header in an H1 tag case 0: e.element.innerHTML = '<h1>' + e.dataItem.header + '<h1>'; break; // // level 1: use template to create details case 1: var html = wjCore.format(this._itemTemplate, e.dataItem, function (data, name, fmt, val) { if (wjCore.isString(data[name])) { val = wjCore.escapeHtml(data[name]); } return val; }); e.element.innerHTML = html; break; } } onSelectedItemChanged() { var node = this._wjTreeViewControl.selectedNode; if (node && node.parentNode) { node = this._wjTreeViewControl.selectedNode = node.parentNode; } node.isCollapsed = false; document.getElementById('selected').textContent = node.dataItem.header; } getTreeData(data) { var treeData = []; for (var i = 0; i < data.length; i++) { treeData.push({ header: data[i].name, detail: [data[i]] }); } return treeData; } componentDidMount() { this._wjTreeViewControl.selectedItem = this._wjTreeViewControl.itemsSource[0]; document.getElementById('selected').textContent = this._wjTreeViewControl.selectedNode.dataItem.header; this._wjTreeViewControl.hostElement.addEventListener("keydown", function (e) { var node = null; switch (e.keyCode) { case wjCore.Key.Up: node = this._wjTreeViewControl.selectedNode.previousSibling(); break; case wjCore.Key.Down: node = this._wjTreeViewControl.selectedNode.nextSibling(); break; } if (node) { this._wjTreeViewControl.selectedNode = node; document.getElementById('selected').textContent = node.dataItem.header; e.preventDefault(); } }); } render() { return (<div className="container-fluid"> <p> Selected item: <b><span id="selected"></span></b> </p> <wjNav.TreeView itemsSource={this.state} displayMemberPath="header" childItemsPath="detail" formatItem={this.onFormatItem.bind(this)} selectedItemChanged={this.onSelectedItemChanged.bind(this)} initialized={this.initTreeView.bind(this)}></wjNav.TreeView> </div>); } } ReactDOM.render(<App />, document.getElementById('app')); <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>AutoComplete</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="node_modules/systemjs/dist/system.src.js"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div id="app"></div> </body> </html> body { font-family: 'Segoe UI'; font-size: 14px; } .item p { margin: 0; } .item h1 { margin-bottom: 0; font-size: 20px; font-weight: 200; } .container-fluid .wj-treeview { margin: 12px; padding: 0 12px 12px 12px; background: #fafafa; box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23); } /* customize TreeView content */ .container-fluid .wj-treeview h1 { display: inline-block; margin-top: 10px; margin-bottom: 0; font-size: 20px; font-weight: 200; } /* hide collapse/expand icons */ .container-fluid .wj-treeview .wj-nodelist .wj-node:before { display: none; } /* remove selected style */ .container-fluid .wj-treeview .wj-node.wj-state-selected { color: inherit; background: transparent; } export function getData() { return [{ name: 'Electro Mart', city: 'Accident', state: 'Maryland', address: '8785 Windfall St.', phone: '(800) 555-1234', fax: '(800) 555-5678', site: 'https://www.electromartnot.com' }, { name: 'Sue\'s Depot', city: 'Big Arm', state: 'Montana', address: '77 Winchester Lane', phone: '(800) 555-2345', fax: '(800) 555-6789', site: 'https://www.suesdepotnot.com' }, { name: 'D&K Digital Locker', city: 'Chicken', state: 'Alaska', address: '787 Lakeview St. ', phone: '(800) 555-3456', fax: '(800) 555-7890', site: 'https://www.digilockernot.com' }, { name: 'Paul\'s Pub & Bistro', city: 'Coupon', state: 'Pennsylvania', address: '711 Old York Drive ', phone: '(800) 555-0987', fax: '(800) 555-6543', site: 'https://www.paulspubnot.com' }, { name: 'Amazing Deals Inc', city: 'Cut And Shoot', state: 'Texas', address: '91 Beech St.', phone: '(800) 955-2109', fax: '(800) 955-8765', site: 'https://www.amazingdealsnot.com' }]; }