|
| 1 | +@ngdoc overview |
| 2 | +@name Tutorial: 319 Complex Trees |
| 3 | +@description The tree feature offers a number of advanced features, care should be taken in |
| 4 | +using these that you understand exactly what you are trying to achieve, and how to utilise |
| 5 | +the tree to achieve that. |
| 6 | + |
| 7 | +Firstly, aggregation. The baseTree provides aggregation logic, this logic is used by grouping |
| 8 | +to provide totals, averages etc. The aggregation logic works well with grouping, as grouping |
| 9 | +creates new blank rows into which the aggregated totals can be written. |
| 10 | + |
| 11 | +With treeView, you can set aggregations, and that aggregation will by default be written into the entity |
| 12 | +for the non-leaf nodes, masking (but not replacing) the data in that column for that entity. |
| 13 | + |
| 14 | +Note also that aggregation only aggregates data from leaf nodes - a node with a $$treeLevel will not |
| 15 | +be aggregated. |
| 16 | + |
| 17 | +There are therefore a few options for how you use aggregation with treeView: |
| 18 | + |
| 19 | +1. You have a column that has data only for leaf nodes, and therefore this data can be aggregated in |
| 20 | +the non-leaf nodes. For example, if you were building a tree representing a file system, the files themselves |
| 21 | +might have sizes, and you might want to aggregate these sizes to show folder size. The folders wouldn't have |
| 22 | +their own sizes, therefore there is an available space to write the aggregation data |
| 23 | +2. You create a custom column that references the same field as an existing column but has it's own |
| 24 | +column name. The aggregations are visible in this column, and you work some magic with the cellTemplates |
| 25 | +to make this work as you desire |
| 26 | +3. You write a custom cellTemplate that shows both the value for that non-leaf node and the aggregated value in |
| 27 | +the same cell. These values are in different places in the entity - the original value is in `entity.fieldName`, |
| 28 | +the aggregated value is in `entity.$$colUid.value` (refer to the treeBase documentation to get more details from |
| 29 | +the aggregation such as the label or the rendered value). You can write a cellTemplate that displays both of these. |
| 30 | +4. You set the `aggregationUpdateEntity` flag on the colDef. The data will then be left in `row.treeNode.aggregations` |
| 31 | +in an array form. You need to process that array to get the data out. You might use this if you're building |
| 32 | +a graph behind the scenes, or something similar. If so, you'd probably look to get the aggregation data out |
| 33 | +of grid.treeBase.tree, rather than extracting it from the rows. You can access the subtree under a given |
| 34 | +row (if you're building a graph for the selected row) through `row.treeNode`. |
| 35 | + |
| 36 | +TreeView doesn't provide a UI for setting aggregations - unlike grouping you cannot select a column and pick an |
| 37 | +aggregation. You can add this UI yourself through custom menu items, but given the above comments |
| 38 | +you need to be careful how you use it. |
| 39 | + |
| 40 | +Aggregations are set on the columnDef or column in the format: |
| 41 | +``` |
| 42 | + colDef.aggregation: { type: uiGridTreeBaseConstants.SUM } |
| 43 | +``` |
| 44 | + |
| 45 | +You can provide a custom aggregation function, but note that the aggregation is performed as a running total, so |
| 46 | +your function needs to work within that framework. Refer the documentation for `customAggregationFn` under |
| 47 | +{@link api/ui.grid.treeBase.api:ColumnDef columnDef}. |
| 48 | + |
| 49 | +You can provide a custom finalizer function, which can be used to format or otherwise complete your aggregation |
| 50 | +once the leaf nodes under a particular node have been aggregated. Refer the documentation for `customAggregationFinalizerFn` |
| 51 | +under {@link api/ui.grid.treeBase.api:ColumnDef columnDef}. |
| 52 | + |
| 53 | +@example |
| 54 | +In this example the balance field is averaged, and then displayed in the standard column using a custom cellTemplate |
| 55 | +that shows both the value for that row as well as the aggregated value. |
| 56 | + |
| 57 | +<example module="app"> |
| 58 | + <file name="app.js"> |
| 59 | + var app = angular.module('app', ['ngAnimate', 'ngTouch', 'ui.grid', 'ui.grid.treeView', 'ui.grid.treeBase' ]); |
| 60 | + |
| 61 | + app.controller('MainCtrl', ['$scope', '$http', '$interval', 'uiGridTreeBaseConstants', function ($scope, $http, $interval, uiGridTreeBaseConstants ) { |
| 62 | + $scope.gridOptions = { |
| 63 | + enableSorting: true, |
| 64 | + enableFiltering: true, |
| 65 | + columnDefs: [ |
| 66 | + { name: 'name', width: '30%' }, |
| 67 | + { name: 'gender', width: '20%' }, |
| 68 | + { name: 'age', width: '20%' }, |
| 69 | + { name: 'company', width: '25%' }, |
| 70 | + { name: 'state', width: '35%', field: 'address.state' }, |
| 71 | + { name: 'balance', width: '25%', aggregation: { type: uiGridTreeBaseConstants.aggregation.SUM }, cellFilter: 'currency', cellTemplate: '<div class="ui-grid-cell-contents" title="TOOLTIP"><span>{{row.entity.balance CUSTOM_FILTERS}}</span><span ng-if="row.entity[\'$$\' + col.uid]"> ({{row.entity["$$" + col.uid].value CUSTOM_FILTERS}})</span></div>' } |
| 72 | + ], |
| 73 | + onRegisterApi: function( gridApi ) { |
| 74 | + $scope.gridApi = gridApi; |
| 75 | + $scope.gridApi.treeBase.on.rowExpanded($scope, function(row) { |
| 76 | + if( row.entity.$$hashKey === $scope.gridOptions.data[50].$$hashKey && !$scope.nodeLoaded ) { |
| 77 | + $interval(function() { |
| 78 | + $scope.gridOptions.data.splice(51,0, |
| 79 | + {name: 'Dynamic 1', gender: 'female', age: 53, company: 'Griddable grids', balance: 38000, $$treeLevel: 1}, |
| 80 | + {name: 'Dynamic 2', gender: 'male', age: 18, company: 'Griddable grids', balance: 29000, $$treeLevel: 1} |
| 81 | + ); |
| 82 | + $scope.nodeLoaded = true; |
| 83 | + }, 2000, 1); |
| 84 | + } |
| 85 | + }); |
| 86 | + } |
| 87 | + }; |
| 88 | + |
| 89 | + $http.get('/data/500_complex.json') |
| 90 | + .success(function(data) { |
| 91 | + for ( var i = 0; i < data.length; i++ ){ |
| 92 | + data[i].balance = Number( data[i].balance.slice(1).replace(/,/,'') ); |
| 93 | + } |
| 94 | + data[0].$$treeLevel = 0; |
| 95 | + data[1].$$treeLevel = 1; |
| 96 | + data[10].$$treeLevel = 1; |
| 97 | + data[20].$$treeLevel = 0; |
| 98 | + data[25].$$treeLevel = 1; |
| 99 | + data[50].$$treeLevel = 0; |
| 100 | + data[51].$$treeLevel = 0; |
| 101 | + $scope.gridOptions.data = data; |
| 102 | + }); |
| 103 | + |
| 104 | + $scope.expandAll = function(){ |
| 105 | + $scope.gridApi.treeBase.expandAllRows(); |
| 106 | + }; |
| 107 | + |
| 108 | + $scope.toggleRow = function( rowNum ){ |
| 109 | + $scope.gridApi.treeBase.toggleRowTreeState($scope.gridApi.grid.renderContainers.body.visibleRowCache[rowNum]); |
| 110 | + }; |
| 111 | + }]); |
| 112 | + </file> |
| 113 | + |
| 114 | + <file name="index.html"> |
| 115 | + <div ng-controller="MainCtrl"> |
| 116 | + <button id="expandAll" type="button" class="btn btn-success" ng-click="expandAll()">Expand All</button> |
| 117 | + <button id="toggleFirstRow" type="button" class="btn btn-success" ng-click="toggleRow(0)">Toggle First Row</button> |
| 118 | + <button id="toggleSecondRow" type="button" class="btn btn-success" ng-click="toggleRow(1)">Toggle Second Row</button> |
| 119 | + <div id="grid1" ui-grid="gridOptions" ui-grid-tree-view class="grid"></div> |
| 120 | + </div> |
| 121 | + </file> |
| 122 | + |
| 123 | + <file name="main.css"> |
| 124 | + .grid { |
| 125 | + width: 500px; |
| 126 | + height: 400px; |
| 127 | + } |
| 128 | + </file> |
| 129 | + <file name="scenario.js"> |
| 130 | + var gridTestUtils = require('../../test/e2e/gridTestUtils.spec.js'); |
| 131 | + describe( '215 tree view', function() { |
| 132 | + }); |
| 133 | + </file> |
| 134 | +</example> |
0 commit comments