Wijmo 5

TreeView入門

このページでは、WijmoのTreeViewコントロールを使い始める方法を示します。

はじめに

TreeViewをAngularJSアプリケーションで使用する手順は以下のとおりです。

  1. AngularJS、Wijmo、およびWijmoのAngularJSディレクティブへの参照を追加します。
  2. appモジュールにWijmo 5ディレクティブを含めます。
    var app = angular.module('app', ['wj']);
  3. データとロジックを提供するコントローラーを追加します。
  4. Wijmo TreeViewコントロールをページに追加してデータにバインドします。
  5. (オプション)コントロールの外観をカスタマイズするCSSを追加します。

以下の例はすべてこれを示しています。

ツリーの作成

ツリーを作成するには、通常3つのプロパティを設定する必要があります。

  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" href="css/bootstrap.css"/> <link rel="stylesheet" href="css/wijmo.css"/> <link href="css/app.css" rel="stylesheet"/> <script src="scripts/angular.js"></script> <script src="scripts/wijmo.js"></script> <script src="scripts/wijmo.nav.js"></script> <script src="scripts/wijmo.angular.js"></script> <script src="scripts/app.js"></script> </head> <body ng-app="app" ng-controller="appCtrl"> <!-- this is the InputNumber directive --> <wj-tree-view control="tv" items-source="items" display-member-path="'header'" child-items-path="'items'" is-animated="isAnimated" auto-collapse="autoCollapse" expandOn-click="expandOnClick"> </wj-tree-view> <div> <button id="btnCollapse" class="btn btn-default" ng-click="tv.collapseToLevel(0);">すべて折りたたみ</button> <button id="btnExpand" class="btn btn-default" ng-click="tv.collapseToLevel(1000);">すべて展開</button> </div> <br /> <label> <input id="chkIsAnimated" type="checkbox" ng-model="tv.isAnimated" /> isAnimated </label> <br /> <label> <input id="chkAutoCollapse" type="checkbox" ng-model="tv.autoCollapse" /> autoCollapse </label> <br /> <label> <input id="chkExpandOnClick" type="checkbox" ng-model="tv.expandOnClick" /> expandOnClick </label> </body> </html>
// declare app module var app = angular.module('app', ['wj']); // app controller provides data app.controller('appCtrl', function appCtrl($scope) { $scope.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' } ] }, { header: 'Toys', img: 'resources/toys.png', items: [ { header: 'Shopkins' }, { header: 'Train Sets' }, { header: 'Science Kit', newItem: true }, { header: 'Play-Doh' }, { header: 'Crayola' } ] }, { header: 'Home', img: 'resources/home.png', items: [ { header: 'Coffeee Maker' }, { header: 'Breadmaker', newItem: true }, { header: 'Solar Panel', newItem: true }, { header: 'Work Table' }, { header: 'Propane Grill' } ] } ]; $scope.isAnimated = true; $scope.autoCollapse = true; $scope.expandOnClick = true; });
/* 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); }

結果(ライブ):




スタイルとCSS

CSSを使用してTreeViewの外観をカスタマイズすることができます。

この例では、折りたたみ/展開アイコンを変更し、ノードレベルに応じて異なるフォントサイズを使用し、レベル1のノードの左側に垂直バーを追加します。

TreeViewの下にあるチェックボックスを使用して、カスタムスタイルを切り替え、その違いを確認します。

<wj-tree-view control="tvCss" ng-class="useCustomCss?'custom-tree':''" items-source="items" display-member-path="'header'" child-items-path="'items'"> </wj-tree-view> <label> <input id="tvCssCheck" type="checkbox" checked="checked" ng-model="useCustomCss" /> カスタムCSSを使用 </label>
$scope.useCustomCss = true;
/* 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); }

結果(ライブ):

ナビゲーションツリー

TreeViewコントロールの最も単純で一般的な使い方はナビゲーションです。TreeViewの階層構造と自動検索機能により、ユーザーはドリルダウンして興味のある項目を簡単に見つけることができます。

ナビゲーションにはselectedItemChangedまたはitemClickedイベントを使用できます。これらの違いは、selectedItemChangedはユーザーがキーボードで選択範囲を移動したときに発生し、itemClickedはユーザーが項目をクリックするかEnterキーを押したときに発生します。

この例では、itemClickedイベントを使用しています。

<wj-tree-view control="tvNav" items-source="items" display-member-path="'header'" child-items-path="'items'" item-clicked="navTo(tvNav)"> </wj-tree-view> <div id="tvNavItem" ng-bind-html="navStr" ></div>
$scope.navStr = ''; $scope.navTo = function (treeView) { $scope.navStr = $sce.trustAsHtml('Navigating to <b>*** ' + treeView.selectedItem.header + ' ***</b>'); $scope.$apply(); }

結果(ライブ):

アコーデオンツリー

アコーディオンは、一度に1つのパネルのみを展開したままにする複数ペインのパネルです。それらは一般的にナビゲーションに使用されます。

TreeViewコントロールを使用してアコーディオンを実装できます。

CSSを使用してヘッダー表示をカスタマイズし、折りたたみ/展開グリフを非表示にします。また、autoCollapseプロパティをtrue(デフォルト)に設定されていることを確認して、非アクティブパネルを自動的に折りたたみます。

<wj-tree-view control="tvAccordion" class="accordion-tree" items-source="accordionItems" is-Content-html="true" auto-Collapse="true"> </wj-tree-view> <div id="tvAccordionItem" ng-bind-html="tvAccordionStr"></div>
$scope.tvAccordionStr = ''; $scope.$watch('tvAccordion', function () { /* handle clicks on accordion items */ if (!$scope.tvAccordion) { return; } $scope.tvAccordion.hostElement.addEventListener('click', function (e) { if (e.target.tagName == 'A') { $scope.tvAccordionStr = $sce.trustAsHtml('Navigating to <b>*** ' + treeView.selectedItem.header + ' ***</b>'); $scope.$apply(); 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; }

結果(ライブ):

チェックボックス

showCheckboxesプロパティをtrueに設定すると、TreeViewは各ノードにチェックボックスを追加します。

チェックボックスが表示されている場合、TreeViewはその階層を管理します。チェックボックスをオンまたはオフにすると、新しい値がすべての子ノードに自動的に適用され、親ノードの状態に反映されます。

アイテムをチェックまたはチェック解除した場合、checkedItemsChangedイベントが発生し、checkedItemsプロパティが現在チェックされているアイテムのリストで更新されます。

<wj-tree-view control="tvChk" items-source="items" display-member-path="'header'" child-items-path="'items'" show-checkboxes="true" checked-items-changed="checkedItems(tvChk)"> </wj-tree-view> <button id="btnCheckAll" class="btn btn-default" ng-click="tvChk.checkAllItems(true);"> すべてチェック </button> <button id="btnUncheckAll" class="btn btn-default" ng-click="tvChk.checkAllItems(false);"> すべてチェック解除 </button>      <button id="btnSaveState" class="btn btn-default" ng-click="saveCheckedItems(tvChk)"> 状態を保存 </button> <button id="btnRestoreState" class="btn btn-default" ng-click="restoreCheckedItems(tvChk)"> 状態を復元 </button> <br/> <div id="tvChkStatus" ng-bind-html="tvChkStatusStr"> </div>
$scope.tvChkStatusStr = ''; $scope.checkedItems = function (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>'; } $scope.tvChkStatusStr = $sce.trustAsHtml(msg); $scope.$apply(); } // save checked items var checkedItems = []; $scope.saveCheckedItems = function(treeView) { checkedItems = treeView.checkedItems; } // restore checked items $scope.restoreCheckedItems = function (treeView) { treeView.checkedItems = checkedItems || []; }

結果(ライブ):

    

画像

imageMemberPathプロパティを使用して、画像URLを含むデータ項目のプロパティの名前を指定してノードに画像を追加します。

たとえば、サンプルではitems配列の「img」プロパティに画像URLが設定されています。

<wj-tree-view control="tvImg" items-source="items" display-member-path="'header'" child-items-path="'items'" image-member-path="'img'"> </wj-tree-view>

結果(ライブ):

無効なノード

TreeNodeのisDisabledプロパティを使用してノードを無効にすることができます。無効なノードは、マウスまたはキーボードを使用して選択することはできません。

<wj-tree-view control="tvDisable" items-source="items" display-member-path="'header'" child-items-path="'items'"> </wj-tree-view> <button id="btnDisableNode" class="btn btn-default" ng-click="disableNode(tvDisable)">選択したノードを無効化</button> <button id="btnEnableAllNodes" class="btn btn-default" ng-click="enableAllNodes(tvDisable)">すべてのノードを有効化</button>
// disable node $scope.disableNode= function (treeView) { var nd = treeView.selectedNode; if (nd) { nd.isDisabled = true; } } // enable all nodes $scope.enableAllNodes = function (treeView) { for (var nd = treeView.getFirstNode(); nd; nd = nd.next()) { nd.isDisabled = false; } }

結果(ライブ):

カスタムノードコンテンツ

formatItemイベントを使用してTreeViewノードのコンテンツをカスタマイズできます。イベントハンドラの引数には、ノードを表す要素と、レンダリングされるデータ項目が含まれます。

以下の例では、formatItemイベントを使用して、ツリー上の新しいアイテムの右側に「new」バッジを追加しています。

<wj-tree-view control="tvFmtItem" items-source="items" display-member-path="'header'" child-items-path="'items'" format-item="formatItem(s, e)"> </wj-tree-view>
// enable all nodes $scope.formatItem = function (s, e) { if (e.dataItem.newItem) { e.element.innerHTML += ' <img style="margin-left:6px;transform:rotate(-30deg)" src="resources/new.png"/>'; } }

結果(ライブ):

遅延読み込み

遅延読み込みは、大きな階層データソースを処理しているときに、データセット全体を一度に読み込む際の遅延を避けたい場合に便利です。

TreeViewコントロールは、非常に簡単に遅延読み込みを実現できます。必要な手順は2つだけです。

  1. 親ノードのデータ項目のitemsプロパティを空の配列に設定します。
  2. TreeViewのlazyLoadFunctionプロパティを、ユーザーがノードを展開したときに呼び出される関数に設定します。この関数は、親ノードと、データが利用可能になったときに呼び出されるコールバック関数の2つの引数をとります。

以下の例のツリーは、3つの遅延読み込みされたノードから始まります。展開すると、lazyLoadFunctionが呼び出されます。この関数は、setTimeoutを使用してhttp遅延をシミュレートし、3つの子ノードのデータを返します。そのうち1つは遅延読み込みされたノードです。

この例では、いくつかのCSSを使用して、ロード中にノードアイコンをアニメーション表示します。

<wj-tree-view control="tvLazyLoad" items-source="lazyItems" display-member-path="'header'" child-items-path="'items'" lazy-load-function="lazyLoadFunction"> </wj-tree-view>
$scope.lazyItems = [ // start with three lazy-loaded nodes { header: 'Lazy Node 1', items: [] }, { header: 'Lazy Node 2', items: [] }, { header: 'Lazy Node 3', items: [] } ]; $scope.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 } }

結果(ライブ):

遅延読み込みとOData

この例は、TreeViewコントロールを使用してODataソースから階層データを表示する方法を示しています。

サンプルはNorthwind employeesテーブルを読み込むことで開始します。データがロードされると、コードは各従業員に空の配列「Orders」を追加します。lazyLoadFunctionは、従業員ノードが展開されたときに注文をロードするために使用されます。

ordersテーブルも、各注文に空の配列「Order_Details」を追加します。lazyLoadFunctionは、注文ノードが展開されたときに注文の詳細を読み込むために使用されます。

<wj-tree-view control="tvLazyLoadOData" display-member-path="['FullName', 'ShipName', 'Summary' ]" child-items-path="['Orders', 'Order_Details']" lazy-load-function="lazyLoadODataFunction"> </wj-tree-view>
$scope.tvLazyLoadOData = null; var nwindService = 'http://services.odata.org/V4/Northwind/Northwind.svc'; $scope.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); } } $scope.$watch('tvLazyLoadOData', function () { 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; }); $scope.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; }

結果(ライブ):

ドラッグ&ドロップ

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

ドラッグが許可されている場合、ユーザーは任意のノードをツリー内の任意の位置にドラッグできます。TreeViewのdrag/dropイベントを処理することによって、この動作をカスタマイズできます。

<wj-tree-view control="tvDragDrop" items-source="items" display-member-path="'header'" child-items-path="'items'" show-checkboxes="true" image-member-path="'img'" allow-dragging="allowDragging" drag-start="dragStart(s,e)" drag-over="dragOver(s,e)"> </wj-tree-view> <label> <input id="allowDragging" type="checkbox" checked="checked" ng-model="tvDragDrop.allowDragging" > allowDragging </label> <br /> <label> <input id="allowDraggingParentNodes" type="checkbox" checked="checked" ng-model="allowDraggingParentNodes"> 親ノードのドラッグを許可 </label> <br /> <label> <input id="allowDroppingIntoEmpty" type="checkbox" checked="checked" ng-model="allowDroppingIntoEmpty"> 空ノードへのドロップを許可 </label>
$scope.allowDraggingParentNodes = true; $scope.allowDroppingIntoEmpty = true; // use dragStart event to honor the allowDraggingParentNodes setting // by setting the 'cancel' event parameter to true $scope.dragStart = function (s, e) { if (e && e.node.hasChildren) { if (!$scope.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' $scope.dragOver = function (s, e) { if (!$scope.allowDroppingIntoEmpty && !e.dropTarget.hasChildren && e.position == wijmo.input.DropPosition.Into) { e.position = wijmo.input.DropPosition.Before; } }

結果(ライブ):



ツリー間のドラッグ&ドロップ

allowDragプロパティをtrueに設定すると、ユーザーはノードをTreeView内の新しい位置にドラッグできます。

異なるTreeViewコントロール間でノードをドラッグ&ドロップできるようにするには、dragOverイベントを処理し、移動が有効な場合はcancel引数をfalseに設定する必要があります。

以下の例では、ユーザーは2つのツリー間でノードをドラッグできます。

<wj-tree-view control="tvDragDrop1" class="short" items-source="dragItems1" display-member-path="'header'" child-items-path="'items'" allow-dragging="true" drag-Over="dragOverBetweenTrees(s,e)"> </wj-tree-view> <wj-tree-view control="tvDragDrop2" class="short" items-source="dragItems2" display-member-path="'header'" child-items-path="'items'" allow-dragging="true" drag-over="dragOverBetweenTrees(s,e)"> </wj-tree-view>
$scope.dragItems1 = [ { header: 'Item 1.1' }, { header: 'Item 1.2' }, { header: 'Item 1.3' } ]; $scope.dragItems2 = [ { header: 'Item 2.1' }, { header: 'Item 2.2' }, { header: 'Item 2.3' } ]; $scope.tvDragDrop1 = null; $scope.tvDragDrop2 = null; // allow drag/drop between tvDragDrop1 and tvDragDrop2 $scope.dragOverBetweenTrees = function (sender, e) { var t1 = e.dragSource.treeView; var t2 = e.dropTarget.treeView; if (t1 == $scope.tvDragDrop1 || t1 == $scope.tvDragDrop2) { if (t2 == $scope.tvDragDrop1 || t2 == $scope.tvDragDrop2) { e.cancel = false; } } }

結果(ライブ):

ノードの編集

TreeViewコントロールは編集をサポートします。isReadOnlyプロパティをfalseに設定すると、ユーザーはF2キーを押してノードの内容を編集できます。

ノードコンテンツに加えられた編集は、displayMemberPathプロパティで指定されたプロパティを使用して、itemsSource配列内のアイテムに自動的に適用されます。

nodeEditStartingnodeEditStartednodeEditEndingnodeEditEndedの各イベントを使用して、編集動作をカスタマイズできます。

以下の例では、子を含まないノードに対してのみ編集を有効にしています。編集するには、ノードを選択してF2キーを押します。

<wj-tree-view control="tvEdit" items-source="editableItems" display-member-path="'header'" child-items-path="'items'" image-member-path="'img'" show-checkboxes="true" is-read-only="false" node-edit-starting="nodeEditStarting(s,e)"> </wj-tree-view>
$scope.nodeEditStarting = function (s, e) { if (e.node.hasChildren) { e.cancel = true; } }

結果(ライブ):

右から左の言語のサポート

一部の言語では、ページの右から左にコンテンツが表示されます(アラビア語やヘブライ語が典型的な例です)。HTMLはこれを'dir'属性で扱います。'dir'を'rtl'に設定すると、すべての要素の内容が右から左に表示されます。

TreeViewはこれを自動的にサポートしています。ツリーをホストする要素の'dir'属性が'rtl'に設定されている場合、ツリーは右から左に展開されるノードでレンダリングされます。コントロールにプロパティを設定する必要はありません。

'dir'属性値は継承されるため、bodyタグに設定すると、ツリーを含むページ全体が右から左にレンダリングされます。

また、CSSには、'dir'要素属性と同じ機能を実行する'direction'属性があります。'dir'属性は、CSS規則で使用できるという事実を含めて、いくつかの理由でより適切と一般的に考えられています。

<wj-tree-view control="tvRtl" items-source="items" display-member-path="'header'" child-items-path="'items'" image-member-path="'img'" show-checkboxes="true"> </wj-tree-view>

結果(ライブ):

親要素はdir="rtl"属性が設定されています