ui-grid.tree-base.js 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657
  1. /*!
  2. * ui-grid - v4.4.6 - 2018-04-06
  3. * Copyright (c) 2018 ; License: MIT
  4. */
  5. (function () {
  6. 'use strict';
  7. /**
  8. * @ngdoc overview
  9. * @name ui.grid.treeBase
  10. * @description
  11. *
  12. * # ui.grid.treeBase
  13. *
  14. * <div class="alert alert-warning" role="alert"><strong>Beta</strong> This feature is ready for testing, but it either hasn't seen a lot of use or has some known bugs.</div>
  15. *
  16. * This module provides base tree handling functions that are shared by other features, notably grouping
  17. * and treeView. It provides a tree view of the data, with nodes in that
  18. * tree and leaves.
  19. *
  20. * Design information:
  21. * -------------------
  22. *
  23. * The raw data that is provided must come with a $$treeLevel on any non-leaf node. Grouping will create
  24. * these on all the group header rows, treeView will expect these to be set in the raw data by the user.
  25. * TreeBase will run a rowsProcessor that:
  26. * - builds `treeBase.tree` out of the provided rows
  27. * - permits a recursive sort of the tree
  28. * - maintains the expand/collapse state of each node
  29. * - provides the expand/collapse all button and the expand/collapse buttons
  30. * - maintains the count of children for each node
  31. *
  32. * Each row is updated with a link to the tree node that represents it. Refer {@link ui.grid.treeBase.grid:treeBase.tree tree documentation}
  33. * for information.
  34. *
  35. * TreeBase adds information to the rows
  36. * - treeLevel: if present and > -1 tells us the level (level 0 is the top level)
  37. * - treeNode: pointer to the node in the grid.treeBase.tree that refers
  38. * to this row, allowing us to manipulate the state
  39. *
  40. * Since the logic is baked into the rowsProcessors, it should get triggered whenever
  41. * row order or filtering or anything like that is changed. We recall the expanded state
  42. * across invocations of the rowsProcessors by the reference to the treeNode on the individual
  43. * rows. We rebuild the tree itself quite frequently, when we do this we use the saved treeNodes to
  44. * get the state, but we overwrite the other data in that treeNode.
  45. *
  46. * By default rows are collapsed, which means all data rows have their visible property
  47. * set to false, and only level 0 group rows are set to visible.
  48. *
  49. * We rely on the rowsProcessors to do the actual expanding and collapsing, so we set the flags we want into
  50. * grid.treeBase.tree, then call refresh. This is because we can't easily change the visible
  51. * row cache without calling the processors, and once we've built the logic into the rowProcessors we may as
  52. * well use it all the time.
  53. *
  54. * Tree base provides sorting (on non-grouped columns).
  55. *
  56. * Sorting works in two passes. The standard sorting is performed for any columns that are important to building
  57. * the tree (for example, any grouped columns). Then after the tree is built, a recursive tree sort is performed
  58. * for the remaining sort columns (including the original sort) - these columns are sorted within each tree level
  59. * (so all the level 1 nodes are sorted, then all the level 2 nodes within each level 1 node etc).
  60. *
  61. * To achieve this we make use of the `ignoreSort` property on the sort configuration. The parent feature (treeView or grouping)
  62. * must provide a rowsProcessor that runs with very low priority (typically in the 60-65 range), and that sets
  63. * the `ignoreSort`on any sort that it wants to run on the tree. TreeBase will clear the ignoreSort on all sorts - so it
  64. * will turn on any sorts that haven't run. It will then call a recursive sort on the tree.
  65. *
  66. * Tree base provides treeAggregation. It checks the treeAggregation configuration on each column, and aggregates based on
  67. * the logic provided as it builds the tree. Footer aggregation from the uiGrid core should not be used with treeBase aggregation,
  68. * since it operates on all visible rows, as opposed to to leaf nodes only. Setting `showColumnFooter: true` will show the
  69. * treeAggregations in the column footer. Aggregation information will be collected in the format:
  70. *
  71. * ```
  72. * {
  73. * type: 'count',
  74. * value: 4,
  75. * label: 'count: ',
  76. * rendered: 'count: 4'
  77. * }
  78. * ```
  79. *
  80. * A callback is provided to format the value once it is finalised (aka a valueFilter).
  81. *
  82. * <br/>
  83. * <br/>
  84. *
  85. * <div doc-module-components="ui.grid.treeBase"></div>
  86. */
  87. var module = angular.module('ui.grid.treeBase', ['ui.grid']);
  88. /**
  89. * @ngdoc object
  90. * @name ui.grid.treeBase.constant:uiGridTreeBaseConstants
  91. *
  92. * @description constants available in treeBase module.
  93. *
  94. * These constants are manually copied into grouping and treeView,
  95. * as I haven't found a way to simply include them, and it's not worth
  96. * investing time in for something that changes very infrequently.
  97. *
  98. */
  99. module.constant('uiGridTreeBaseConstants', {
  100. featureName: "treeBase",
  101. rowHeaderColName: 'treeBaseRowHeaderCol',
  102. EXPANDED: 'expanded',
  103. COLLAPSED: 'collapsed',
  104. aggregation: {
  105. COUNT: 'count',
  106. SUM: 'sum',
  107. MAX: 'max',
  108. MIN: 'min',
  109. AVG: 'avg'
  110. }
  111. });
  112. /**
  113. * @ngdoc service
  114. * @name ui.grid.treeBase.service:uiGridTreeBaseService
  115. *
  116. * @description Services for treeBase feature
  117. */
  118. /**
  119. * @ngdoc object
  120. * @name ui.grid.treeBase.api:ColumnDef
  121. *
  122. * @description ColumnDef for tree feature, these are available to be
  123. * set using the ui-grid {@link ui.grid.class:GridOptions.columnDef gridOptions.columnDefs}
  124. */
  125. module.service('uiGridTreeBaseService', ['$q', 'uiGridTreeBaseConstants', 'gridUtil', 'GridRow', 'gridClassFactory', 'i18nService', 'uiGridConstants', 'rowSorter',
  126. function ($q, uiGridTreeBaseConstants, gridUtil, GridRow, gridClassFactory, i18nService, uiGridConstants, rowSorter) {
  127. var service = {
  128. initializeGrid: function (grid, $scope) {
  129. //add feature namespace and any properties to grid for needed
  130. /**
  131. * @ngdoc object
  132. * @name ui.grid.treeBase.grid:treeBase
  133. *
  134. * @description Grid properties and functions added for treeBase
  135. */
  136. grid.treeBase = {};
  137. /**
  138. * @ngdoc property
  139. * @propertyOf ui.grid.treeBase.grid:treeBase
  140. * @name numberLevels
  141. *
  142. * @description Total number of tree levels currently used, calculated by the rowsProcessor by
  143. * retaining the highest tree level it sees
  144. */
  145. grid.treeBase.numberLevels = 0;
  146. /**
  147. * @ngdoc property
  148. * @propertyOf ui.grid.treeBase.grid:treeBase
  149. * @name expandAll
  150. *
  151. * @description Whether or not the expandAll box is selected
  152. */
  153. grid.treeBase.expandAll = false;
  154. /**
  155. * @ngdoc property
  156. * @propertyOf ui.grid.treeBase.grid:treeBase
  157. * @name tree
  158. *
  159. * @description Tree represented as a nested array that holds the state of each node, along with a
  160. * pointer to the row. The array order is material - we will display the children in the order
  161. * they are stored in the array
  162. *
  163. * Each node stores:
  164. *
  165. * - the state of this node
  166. * - an array of children of this node
  167. * - a pointer to the parent of this node (reverse pointer, allowing us to walk up the tree)
  168. * - the number of children of this node
  169. * - aggregation information calculated from the nodes
  170. *
  171. * ```
  172. * [{
  173. * state: 'expanded',
  174. * row: <reference to row>,
  175. * parentRow: null,
  176. * aggregations: [{
  177. * type: 'count',
  178. * col: <gridCol>,
  179. * value: 2,
  180. * label: 'count: ',
  181. * rendered: 'count: 2'
  182. * }],
  183. * children: [
  184. * {
  185. * state: 'expanded',
  186. * row: <reference to row>,
  187. * parentRow: <reference to row>,
  188. * aggregations: [{
  189. * type: 'count',
  190. * col: '<gridCol>,
  191. * value: 4,
  192. * label: 'count: ',
  193. * rendered: 'count: 4'
  194. * }],
  195. * children: [
  196. * { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
  197. * { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> },
  198. * { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
  199. * { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> }
  200. * ]
  201. * },
  202. * {
  203. * state: 'collapsed',
  204. * row: <reference to row>,
  205. * parentRow: <reference to row>,
  206. * aggregations: [{
  207. * type: 'count',
  208. * col: <gridCol>,
  209. * value: 3,
  210. * label: 'count: ',
  211. * rendered: 'count: 3'
  212. * }],
  213. * children: [
  214. * { state: 'expanded', row: <reference to row>, parentRow: <reference to row> },
  215. * { state: 'collapsed', row: <reference to row>, parentRow: <reference to row> },
  216. * { state: 'expanded', row: <reference to row>, parentRow: <reference to row> }
  217. * ]
  218. * }
  219. * ]
  220. * }, {<another level 0 node maybe>} ]
  221. * ```
  222. * Missing state values are false - meaning they aren't expanded.
  223. *
  224. * This is used because the rowProcessors run every time the grid is refreshed, so
  225. * we'd lose the expanded state every time the grid was refreshed. This instead gives
  226. * us a reliable lookup that persists across rowProcessors.
  227. *
  228. * This tree is rebuilt every time we run the rowsProcessors. Since each row holds a pointer
  229. * to it's tree node we can persist expand/collapse state across calls to rowsProcessor, we discard
  230. * all transient information on the tree (children, childCount) and recalculate it
  231. *
  232. */
  233. grid.treeBase.tree = [];
  234. service.defaultGridOptions(grid.options);
  235. grid.registerRowsProcessor(service.treeRows, 410);
  236. grid.registerColumnBuilder( service.treeBaseColumnBuilder );
  237. service.createRowHeader( grid );
  238. /**
  239. * @ngdoc object
  240. * @name ui.grid.treeBase.api:PublicApi
  241. *
  242. * @description Public Api for treeBase feature
  243. */
  244. var publicApi = {
  245. events: {
  246. treeBase: {
  247. /**
  248. * @ngdoc event
  249. * @eventOf ui.grid.treeBase.api:PublicApi
  250. * @name rowExpanded
  251. * @description raised whenever a row is expanded. If you are dynamically
  252. * rendering your tree you can listen to this event, and then retrieve
  253. * the children of this row and load them into the grid data.
  254. *
  255. * When the data is loaded the grid will automatically refresh to show these new rows
  256. *
  257. * <pre>
  258. * gridApi.treeBase.on.rowExpanded(scope,function(row){})
  259. * </pre>
  260. * @param {gridRow} row the row that was expanded. You can also
  261. * retrieve the grid from this row with row.grid
  262. */
  263. rowExpanded: {},
  264. /**
  265. * @ngdoc event
  266. * @eventOf ui.grid.treeBase.api:PublicApi
  267. * @name rowCollapsed
  268. * @description raised whenever a row is collapsed. Doesn't really have
  269. * a purpose at the moment, included for symmetry
  270. *
  271. * <pre>
  272. * gridApi.treeBase.on.rowCollapsed(scope,function(row){})
  273. * </pre>
  274. * @param {gridRow} row the row that was collapsed. You can also
  275. * retrieve the grid from this row with row.grid
  276. */
  277. rowCollapsed: {}
  278. }
  279. },
  280. methods: {
  281. treeBase: {
  282. /**
  283. * @ngdoc function
  284. * @name expandAllRows
  285. * @methodOf ui.grid.treeBase.api:PublicApi
  286. * @description Expands all tree rows
  287. */
  288. expandAllRows: function () {
  289. service.expandAllRows(grid);
  290. },
  291. /**
  292. * @ngdoc function
  293. * @name collapseAllRows
  294. * @methodOf ui.grid.treeBase.api:PublicApi
  295. * @description collapse all tree rows
  296. */
  297. collapseAllRows: function () {
  298. service.collapseAllRows(grid);
  299. },
  300. /**
  301. * @ngdoc function
  302. * @name toggleRowTreeState
  303. * @methodOf ui.grid.treeBase.api:PublicApi
  304. * @description call expand if the row is collapsed, collapse if it is expanded
  305. * @param {gridRow} row the row you wish to toggle
  306. */
  307. toggleRowTreeState: function (row) {
  308. service.toggleRowTreeState(grid, row);
  309. },
  310. /**
  311. * @ngdoc function
  312. * @name expandRow
  313. * @methodOf ui.grid.treeBase.api:PublicApi
  314. * @description expand the immediate children of the specified row
  315. * @param {gridRow} row the row you wish to expand
  316. * @param {boolean} recursive true if you wish to expand the row's ancients
  317. */
  318. expandRow: function (row, recursive) {
  319. service.expandRow(grid, row, recursive);
  320. },
  321. /**
  322. * @ngdoc function
  323. * @name expandRowChildren
  324. * @methodOf ui.grid.treeBase.api:PublicApi
  325. * @description expand all children of the specified row
  326. * @param {gridRow} row the row you wish to expand
  327. */
  328. expandRowChildren: function (row) {
  329. service.expandRowChildren(grid, row);
  330. },
  331. /**
  332. * @ngdoc function
  333. * @name collapseRow
  334. * @methodOf ui.grid.treeBase.api:PublicApi
  335. * @description collapse the specified row. When
  336. * you expand the row again, all grandchildren will retain their state
  337. * @param {gridRow} row the row you wish to collapse
  338. */
  339. collapseRow: function ( row ) {
  340. service.collapseRow(grid, row);
  341. },
  342. /**
  343. * @ngdoc function
  344. * @name collapseRowChildren
  345. * @methodOf ui.grid.treeBase.api:PublicApi
  346. * @description collapse all children of the specified row. When
  347. * you expand the row again, all grandchildren will be collapsed
  348. * @param {gridRow} row the row you wish to collapse children for
  349. */
  350. collapseRowChildren: function ( row ) {
  351. service.collapseRowChildren(grid, row);
  352. },
  353. /**
  354. * @ngdoc function
  355. * @name getTreeState
  356. * @methodOf ui.grid.treeBase.api:PublicApi
  357. * @description Get the tree state for this grid,
  358. * used by the saveState feature
  359. * Returned treeState as an object
  360. * `{ expandedState: { uid: 'expanded', uid: 'collapsed' } }`
  361. * where expandedState is a hash of row uid and the current expanded state
  362. *
  363. * @returns {object} tree state
  364. *
  365. * TODO - this needs work - we need an identifier that persists across instantiations,
  366. * not uid. This really means we need a row identity defined, but that won't work for
  367. * grouping. Perhaps this needs to be moved up to treeView and grouping, rather than
  368. * being in base.
  369. */
  370. getTreeExpandedState: function () {
  371. return { expandedState: service.getTreeState(grid) };
  372. },
  373. /**
  374. * @ngdoc function
  375. * @name setTreeState
  376. * @methodOf ui.grid.treeBase.api:PublicApi
  377. * @description Set the expanded states of the tree
  378. * @param {object} config the config you want to apply, in the format
  379. * provided by getTreeState
  380. */
  381. setTreeState: function ( config ) {
  382. service.setTreeState( grid, config );
  383. },
  384. /**
  385. * @ngdoc function
  386. * @name getRowChildren
  387. * @methodOf ui.grid.treeBase.api:PublicApi
  388. * @description Get the children of the specified row
  389. * @param {GridRow} row the row you want the children of
  390. * @returns {Array} array of children of this row, the children
  391. * are all gridRows
  392. */
  393. getRowChildren: function ( row ){
  394. return row.treeNode.children.map( function( childNode ){
  395. return childNode.row;
  396. });
  397. }
  398. }
  399. }
  400. };
  401. grid.api.registerEventsFromObject(publicApi.events);
  402. grid.api.registerMethodsFromObject(publicApi.methods);
  403. },
  404. defaultGridOptions: function (gridOptions) {
  405. //default option to true unless it was explicitly set to false
  406. /**
  407. * @ngdoc object
  408. * @name ui.grid.treeBase.api:GridOptions
  409. *
  410. * @description GridOptions for treeBase feature, these are available to be
  411. * set using the ui-grid {@link ui.grid.class:GridOptions gridOptions}
  412. */
  413. /**
  414. * @ngdoc object
  415. * @name treeRowHeaderBaseWidth
  416. * @propertyOf ui.grid.treeBase.api:GridOptions
  417. * @description Base width of the tree header, provides for a single level of tree. This
  418. * is incremented by `treeIndent` for each extra level
  419. * <br/>Defaults to 30
  420. */
  421. gridOptions.treeRowHeaderBaseWidth = gridOptions.treeRowHeaderBaseWidth || 30;
  422. /**
  423. * @ngdoc object
  424. * @name treeIndent
  425. * @propertyOf ui.grid.treeBase.api:GridOptions
  426. * @description Number of pixels of indent for the icon at each tree level, wider indents are visually more pleasing,
  427. * but will make the tree row header wider
  428. * <br/>Defaults to 10
  429. */
  430. gridOptions.treeIndent = gridOptions.treeIndent || 10;
  431. /**
  432. * @ngdoc object
  433. * @name showTreeRowHeader
  434. * @propertyOf ui.grid.treeBase.api:GridOptions
  435. * @description If set to false, don't create the row header. You'll need to programmatically control the expand
  436. * states
  437. * <br/>Defaults to true
  438. */
  439. gridOptions.showTreeRowHeader = gridOptions.showTreeRowHeader !== false;
  440. /**
  441. * @ngdoc object
  442. * @name showTreeExpandNoChildren
  443. * @propertyOf ui.grid.treeBase.api:GridOptions
  444. * @description If set to true, show the expand/collapse button even if there are no
  445. * children of a node. You'd use this if you're planning to dynamically load the children
  446. *
  447. * <br/>Defaults to true, grouping overrides to false
  448. */
  449. gridOptions.showTreeExpandNoChildren = gridOptions.showTreeExpandNoChildren !== false;
  450. /**
  451. * @ngdoc object
  452. * @name treeRowHeaderAlwaysVisible
  453. * @propertyOf ui.grid.treeBase.api:GridOptions
  454. * @description If set to true, row header even if there are no tree nodes
  455. *
  456. * <br/>Defaults to true
  457. */
  458. gridOptions.treeRowHeaderAlwaysVisible = gridOptions.treeRowHeaderAlwaysVisible !== false;
  459. /**
  460. * @ngdoc object
  461. * @name treeCustomAggregations
  462. * @propertyOf ui.grid.treeBase.api:GridOptions
  463. * @description Define custom aggregation functions. The properties of this object will be
  464. * aggregation types available for use on columnDef with {@link ui.grid.treeBase.api:ColumnDef treeAggregationType} or through the column menu.
  465. * If a function defined here uses the same name as one of the native aggregations, this one will take precedence.
  466. * The object format is:
  467. *
  468. * <pre>
  469. * {
  470. * aggregationName: {
  471. * label: (optional) string,
  472. * aggregationFn: function( aggregation, fieldValue, numValue, row ){...},
  473. * finalizerFn: (optional) function( aggregation ){...}
  474. * },
  475. * mean: {
  476. * label: 'mean',
  477. * aggregationFn: function( aggregation, fieldValue, numValue ){
  478. * aggregation.count = (aggregation.count || 1) + 1;
  479. * aggregation.sum = (aggregation.sum || 0) + numValue;
  480. * },
  481. * finalizerFn: function( aggregation ){
  482. * aggregation.value = aggregation.sum / aggregation.count
  483. * }
  484. * }
  485. * }
  486. * </pre>
  487. *
  488. * <br/>The `finalizerFn` may be used to manipulate the value before rendering, or to
  489. * apply a custom rendered value. If `aggregation.rendered` is left undefined, the value will be
  490. * rendered. Note that the native aggregation functions use an `finalizerFn` to concatenate
  491. * the label and the value.
  492. *
  493. * <br/>Defaults to {}
  494. */
  495. gridOptions.treeCustomAggregations = gridOptions.treeCustomAggregations || {};
  496. /**
  497. * @ngdoc object
  498. * @name enableExpandAll
  499. * @propertyOf ui.grid.treeBase.api:GridOptions
  500. * @description Enable the expand all button at the top of the row header
  501. *
  502. * <br/>Defaults to true
  503. */
  504. gridOptions.enableExpandAll = gridOptions.enableExpandAll !== false;
  505. },
  506. /**
  507. * @ngdoc function
  508. * @name treeBaseColumnBuilder
  509. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  510. * @description Sets the tree defaults based on the columnDefs
  511. *
  512. * @param {object} colDef columnDef we're basing on
  513. * @param {GridColumn} col the column we're to update
  514. * @param {object} gridOptions the options we should use
  515. * @returns {promise} promise for the builder - actually we do it all inline so it's immediately resolved
  516. */
  517. treeBaseColumnBuilder: function (colDef, col, gridOptions) {
  518. /**
  519. * @ngdoc object
  520. * @name customTreeAggregationFn
  521. * @propertyOf ui.grid.treeBase.api:ColumnDef
  522. * @description A custom function that aggregates rows into some form of
  523. * total. Aggregations run row-by-row, the function needs to be capable of
  524. * creating a running total.
  525. *
  526. * The function will be provided the aggregation item (in which you can store running
  527. * totals), the row value that is to be aggregated, and that same row value converted to
  528. * a number (most aggregations work on numbers)
  529. * @example
  530. * <pre>
  531. * customTreeAggregationFn = function ( aggregation, fieldValue, numValue, row ){
  532. * // calculates the average of the squares of the values
  533. * if ( typeof(aggregation.count) === 'undefined' ){
  534. * aggregation.count = 0;
  535. * }
  536. * aggregation.count++;
  537. *
  538. * if ( !isNaN(numValue) ){
  539. * if ( typeof(aggregation.total) === 'undefined' ){
  540. * aggregation.total = 0;
  541. * }
  542. * aggregation.total = aggregation.total + numValue * numValue;
  543. * }
  544. *
  545. * aggregation.value = aggregation.total / aggregation.count;
  546. * }
  547. * </pre>
  548. * <br/>Defaults to undefined. May be overwritten by treeAggregationType, the two options should not be used together.
  549. */
  550. if ( typeof(colDef.customTreeAggregationFn) !== 'undefined' ){
  551. col.treeAggregationFn = colDef.customTreeAggregationFn;
  552. }
  553. /**
  554. * @ngdoc object
  555. * @name treeAggregationType
  556. * @propertyOf ui.grid.treeBase.api:ColumnDef
  557. * @description Use one of the native or grid-level aggregation methods for calculating aggregations on this column.
  558. * Native method are in the constants file and include: SUM, COUNT, MIN, MAX, AVG. This may also be the property the
  559. * name of an aggregation function defined with {@link ui.grid.treeBase.api:GridOptions treeCustomAggregations}.
  560. *
  561. * <pre>
  562. * treeAggregationType = uiGridTreeBaseConstants.aggregation.SUM,
  563. * }
  564. * </pre>
  565. *
  566. * If you are using aggregations you should either:
  567. *
  568. * - also use grouping, in which case the aggregations are displayed in the group header, OR
  569. * - use treeView, in which case you can set `treeAggregationUpdateEntity: true` in the colDef, and
  570. * treeBase will store the aggregation information in the entity, or you can set `treeAggregationUpdateEntity: false`
  571. * in the colDef, and you need to manual retrieve the calculated aggregations from the row.treeNode.aggregations
  572. *
  573. * <br/>Takes precendence over a treeAggregationFn, the two options should not be used together.
  574. * <br/>Defaults to undefined.
  575. */
  576. if ( typeof(colDef.treeAggregationType) !== 'undefined' ){
  577. col.treeAggregation = { type: colDef.treeAggregationType };
  578. if ( typeof(gridOptions.treeCustomAggregations[colDef.treeAggregationType]) !== 'undefined' ){
  579. col.treeAggregationFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].aggregationFn;
  580. col.treeAggregationFinalizerFn = gridOptions.treeCustomAggregations[colDef.treeAggregationType].finalizerFn;
  581. col.treeAggregation.label = gridOptions.treeCustomAggregations[colDef.treeAggregationType].label;
  582. } else if ( typeof(service.nativeAggregations()[colDef.treeAggregationType]) !== 'undefined' ){
  583. col.treeAggregationFn = service.nativeAggregations()[colDef.treeAggregationType].aggregationFn;
  584. col.treeAggregation.label = service.nativeAggregations()[colDef.treeAggregationType].label;
  585. }
  586. }
  587. /**
  588. * @ngdoc object
  589. * @name treeAggregationLabel
  590. * @propertyOf ui.grid.treeBase.api:ColumnDef
  591. * @description A custom label to use for this aggregation. If provided we don't use native i18n.
  592. */
  593. if ( typeof(colDef.treeAggregationLabel) !== 'undefined' ){
  594. if (typeof(col.treeAggregation) === 'undefined' ){
  595. col.treeAggregation = {};
  596. }
  597. col.treeAggregation.label = colDef.treeAggregationLabel;
  598. }
  599. /**
  600. * @ngdoc object
  601. * @name treeAggregationUpdateEntity
  602. * @propertyOf ui.grid.treeBase.api:ColumnDef
  603. * @description Store calculated aggregations into the entity, allowing them
  604. * to be displayed in the grid using a standard cellTemplate. This defaults to true,
  605. * if you are using grouping then you shouldn't set it to false, as then the aggregations won't
  606. * display.
  607. *
  608. * If you are using treeView in most cases you'll want to set this to true. This will result in
  609. * getCellValue returning the aggregation rather than whatever was stored in the cell attribute on
  610. * the entity. If you want to render the underlying entity value (and do something else with the aggregation)
  611. * then you could use a custom cellTemplate to display `row.entity.myAttribute`, rather than using getCellValue.
  612. *
  613. * <br/>Defaults to true
  614. *
  615. * @example
  616. * <pre>
  617. * gridOptions.columns = [{
  618. * name: 'myCol',
  619. * treeAggregation: { type: uiGridTreeBaseConstants.aggregation.SUM },
  620. * treeAggregationUpdateEntity: true
  621. * cellTemplate: '<div>{{row.entity.myCol + " " + row.treeNode.aggregations[0].rendered}}</div>'
  622. * }];
  623. * </pre>
  624. */
  625. col.treeAggregationUpdateEntity = colDef.treeAggregationUpdateEntity !== false;
  626. /**
  627. * @ngdoc object
  628. * @name customTreeAggregationFinalizerFn
  629. * @propertyOf ui.grid.treeBase.api:ColumnDef
  630. * @description A custom function that populates aggregation.rendered, this is called when
  631. * a particular aggregation has been fully calculated, and we want to render the value.
  632. *
  633. * With the native aggregation options we just concatenate `aggregation.label` and
  634. * `aggregation.value`, but if you wanted to apply a filter or otherwise manipulate the label
  635. * or the value, you can do so with this function. This function will be called after the
  636. * the default `finalizerFn`.
  637. *
  638. * @example
  639. * <pre>
  640. * customTreeAggregationFinalizerFn = function ( aggregation ){
  641. * aggregation.rendered = aggregation.label + aggregation.value / 100 + '%';
  642. * }
  643. * </pre>
  644. * <br/>Defaults to undefined.
  645. */
  646. if ( typeof(col.customTreeAggregationFinalizerFn) === 'undefined' ){
  647. col.customTreeAggregationFinalizerFn = colDef.customTreeAggregationFinalizerFn;
  648. }
  649. },
  650. /**
  651. * @ngdoc function
  652. * @name createRowHeader
  653. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  654. * @description Create the rowHeader. If treeRowHeaderAlwaysVisible then
  655. * set it to visible, otherwise set it to invisible
  656. *
  657. * @param {Grid} grid grid object
  658. */
  659. createRowHeader: function( grid ){
  660. var rowHeaderColumnDef = {
  661. name: uiGridTreeBaseConstants.rowHeaderColName,
  662. displayName: '',
  663. width: grid.options.treeRowHeaderBaseWidth,
  664. minWidth: 10,
  665. cellTemplate: 'ui-grid/treeBaseRowHeader',
  666. headerCellTemplate: 'ui-grid/treeBaseHeaderCell',
  667. enableColumnResizing: false,
  668. enableColumnMenu: false,
  669. exporterSuppressExport: true,
  670. allowCellFocus: true
  671. };
  672. rowHeaderColumnDef.visible = grid.options.treeRowHeaderAlwaysVisible;
  673. grid.addRowHeaderColumn(rowHeaderColumnDef, -100);
  674. },
  675. /**
  676. * @ngdoc function
  677. * @name expandAllRows
  678. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  679. * @description Expands all nodes in the tree
  680. *
  681. * @param {Grid} grid grid object
  682. */
  683. expandAllRows: function (grid) {
  684. grid.treeBase.tree.forEach( function( node ) {
  685. service.setAllNodes( grid, node, uiGridTreeBaseConstants.EXPANDED);
  686. });
  687. grid.treeBase.expandAll = true;
  688. grid.queueGridRefresh();
  689. },
  690. /**
  691. * @ngdoc function
  692. * @name collapseAllRows
  693. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  694. * @description Collapses all nodes in the tree
  695. *
  696. * @param {Grid} grid grid object
  697. */
  698. collapseAllRows: function (grid) {
  699. grid.treeBase.tree.forEach( function( node ) {
  700. service.setAllNodes( grid, node, uiGridTreeBaseConstants.COLLAPSED);
  701. });
  702. grid.treeBase.expandAll = false;
  703. grid.queueGridRefresh();
  704. },
  705. /**
  706. * @ngdoc function
  707. * @name setAllNodes
  708. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  709. * @description Works through a subset of grid.treeBase.rowExpandedStates, setting
  710. * all child nodes (and their descendents) of the provided node to the given state.
  711. *
  712. * Calls itself recursively on all nodes so as to achieve this.
  713. *
  714. * @param {Grid} grid the grid we're operating on (so we can raise events)
  715. * @param {object} treeNode a node in the tree that we want to update
  716. * @param {string} targetState the state we want to set it to
  717. */
  718. setAllNodes: function (grid, treeNode, targetState) {
  719. if ( typeof(treeNode.state) !== 'undefined' && treeNode.state !== targetState ){
  720. treeNode.state = targetState;
  721. if ( targetState === uiGridTreeBaseConstants.EXPANDED ){
  722. grid.api.treeBase.raise.rowExpanded(treeNode.row);
  723. } else {
  724. grid.api.treeBase.raise.rowCollapsed(treeNode.row);
  725. }
  726. }
  727. // set all child nodes
  728. if ( treeNode.children ){
  729. treeNode.children.forEach(function( childNode ){
  730. service.setAllNodes(grid, childNode, targetState);
  731. });
  732. }
  733. },
  734. /**
  735. * @ngdoc function
  736. * @name toggleRowTreeState
  737. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  738. * @description Toggles the expand or collapse state of this grouped row, if
  739. * it's a parent row
  740. *
  741. * @param {Grid} grid grid object
  742. * @param {GridRow} row the row we want to toggle
  743. */
  744. toggleRowTreeState: function ( grid, row ){
  745. if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
  746. return;
  747. }
  748. if (row.treeNode.state === uiGridTreeBaseConstants.EXPANDED){
  749. service.collapseRow(grid, row);
  750. } else {
  751. service.expandRow(grid, row, false);
  752. }
  753. grid.queueGridRefresh();
  754. },
  755. /**
  756. * @ngdoc function
  757. * @name expandRow
  758. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  759. * @description Expands this specific row, showing only immediate children.
  760. *
  761. * @param {Grid} grid grid object
  762. * @param {GridRow} row the row we want to expand
  763. * @param {boolean} recursive true if you wish to expand the row's ancients
  764. */
  765. expandRow: function ( grid, row, recursive ){
  766. if ( recursive ){
  767. var parents = [];
  768. while ( row && typeof(row.treeLevel) !== 'undefined' && row.treeLevel !== null && row.treeLevel >= 0 && row.treeNode.state !== uiGridTreeBaseConstants.EXPANDED ){
  769. parents.push(row);
  770. row = row.treeNode.parentRow;
  771. }
  772. if ( parents.length > 0 ){
  773. row = parents.pop();
  774. while ( row ){
  775. row.treeNode.state = uiGridTreeBaseConstants.EXPANDED;
  776. grid.api.treeBase.raise.rowExpanded(row);
  777. row = parents.pop();
  778. }
  779. grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
  780. grid.queueGridRefresh();
  781. }
  782. } else {
  783. if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
  784. return;
  785. }
  786. if ( row.treeNode.state !== uiGridTreeBaseConstants.EXPANDED ){
  787. row.treeNode.state = uiGridTreeBaseConstants.EXPANDED;
  788. grid.api.treeBase.raise.rowExpanded(row);
  789. grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
  790. grid.queueGridRefresh();
  791. }
  792. }
  793. },
  794. /**
  795. * @ngdoc function
  796. * @name expandRowChildren
  797. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  798. * @description Expands this specific row, showing all children.
  799. *
  800. * @param {Grid} grid grid object
  801. * @param {GridRow} row the row we want to expand
  802. */
  803. expandRowChildren: function ( grid, row ){
  804. if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
  805. return;
  806. }
  807. service.setAllNodes(grid, row.treeNode, uiGridTreeBaseConstants.EXPANDED);
  808. grid.treeBase.expandAll = service.allExpanded(grid.treeBase.tree);
  809. grid.queueGridRefresh();
  810. },
  811. /**
  812. * @ngdoc function
  813. * @name collapseRow
  814. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  815. * @description Collapses this specific row
  816. *
  817. * @param {Grid} grid grid object
  818. * @param {GridRow} row the row we want to collapse
  819. */
  820. collapseRow: function( grid, row ){
  821. if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
  822. return;
  823. }
  824. if ( row.treeNode.state !== uiGridTreeBaseConstants.COLLAPSED ){
  825. row.treeNode.state = uiGridTreeBaseConstants.COLLAPSED;
  826. grid.treeBase.expandAll = false;
  827. grid.api.treeBase.raise.rowCollapsed(row);
  828. grid.queueGridRefresh();
  829. }
  830. },
  831. /**
  832. * @ngdoc function
  833. * @name collapseRowChildren
  834. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  835. * @description Collapses this specific row and all children
  836. *
  837. * @param {Grid} grid grid object
  838. * @param {GridRow} row the row we want to collapse
  839. */
  840. collapseRowChildren: function( grid, row ){
  841. if ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ){
  842. return;
  843. }
  844. service.setAllNodes(grid, row.treeNode, uiGridTreeBaseConstants.COLLAPSED);
  845. grid.treeBase.expandAll = false;
  846. grid.queueGridRefresh();
  847. },
  848. /**
  849. * @ngdoc function
  850. * @name allExpanded
  851. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  852. * @description Returns true if all rows are expanded, false
  853. * if they're not. Walks the tree to determine this. Used
  854. * to set the expandAll state.
  855. *
  856. * If the node has no children, then return true (it's immaterial
  857. * whether it is expanded). If the node has children, then return
  858. * false if this node is collapsed, or if any child node is not all expanded
  859. *
  860. * @param {object} tree the grid to check
  861. * @returns {boolean} whether or not the tree is all expanded
  862. */
  863. allExpanded: function( tree ){
  864. var allExpanded = true;
  865. tree.forEach( function( node ){
  866. if ( !service.allExpandedInternal( node ) ){
  867. allExpanded = false;
  868. }
  869. });
  870. return allExpanded;
  871. },
  872. allExpandedInternal: function( treeNode ){
  873. if ( treeNode.children && treeNode.children.length > 0 ){
  874. if ( treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
  875. return false;
  876. }
  877. var allExpanded = true;
  878. treeNode.children.forEach( function( node ){
  879. if ( !service.allExpandedInternal( node ) ){
  880. allExpanded = false;
  881. }
  882. });
  883. return allExpanded;
  884. } else {
  885. return true;
  886. }
  887. },
  888. /**
  889. * @ngdoc function
  890. * @name treeRows
  891. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  892. * @description The rowProcessor that adds the nodes to the tree, and sets the visible
  893. * state of each row based on it's parent state
  894. *
  895. * Assumes it is always called after the sorting processor, and the grouping processor if there is one.
  896. * Performs any tree sorts itself after having built the tree
  897. *
  898. * Processes all the rows in order, setting the group level based on the $$treeLevel in the associated
  899. * entity, and setting the visible state based on the parent's state.
  900. *
  901. * Calculates the deepest level of tree whilst it goes, and updates that so that the header column can be correctly
  902. * sized.
  903. *
  904. * Aggregates if necessary along the way.
  905. *
  906. * @param {array} renderableRows the rows we want to process, usually the output from the previous rowProcessor
  907. * @returns {array} the updated rows
  908. */
  909. treeRows: function( renderableRows ) {
  910. if (renderableRows.length === 0){
  911. return renderableRows;
  912. }
  913. var grid = this;
  914. grid.treeBase.tree = service.createTree( grid, renderableRows );
  915. service.updateRowHeaderWidth( grid );
  916. service.sortTree( grid );
  917. service.fixFilter( grid );
  918. return service.renderTree( grid.treeBase.tree );
  919. },
  920. /**
  921. * @ngdoc function
  922. * @name createOrUpdateRowHeaderWidth
  923. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  924. * @description Calculates the rowHeader width.
  925. *
  926. * If rowHeader is always present, updates the width.
  927. *
  928. * If rowHeader is only sometimes present (`treeRowHeaderAlwaysVisible: false`), determines whether there
  929. * should be one, then creates or removes it as appropriate, with the created rowHeader having the
  930. * right width.
  931. *
  932. * If there's never a rowHeader then never creates one: `showTreeRowHeader: false`
  933. *
  934. * @param {Grid} grid the grid we want to set the row header on
  935. */
  936. updateRowHeaderWidth: function( grid ){
  937. var rowHeader = grid.getColumn(uiGridTreeBaseConstants.rowHeaderColName);
  938. var newWidth = grid.options.treeRowHeaderBaseWidth + grid.options.treeIndent * Math.max(grid.treeBase.numberLevels - 1, 0);
  939. if ( rowHeader && newWidth !== rowHeader.width ){
  940. rowHeader.width = newWidth;
  941. grid.queueRefresh();
  942. }
  943. var newVisibility = true;
  944. if ( grid.options.showTreeRowHeader === false ){
  945. newVisibility = false;
  946. }
  947. if ( grid.options.treeRowHeaderAlwaysVisible === false && grid.treeBase.numberLevels <= 0 ){
  948. newVisibility = false;
  949. }
  950. if ( rowHeader && rowHeader.visible !== newVisibility ) {
  951. rowHeader.visible = newVisibility;
  952. rowHeader.colDef.visible = newVisibility;
  953. grid.queueGridRefresh();
  954. }
  955. },
  956. /**
  957. * @ngdoc function
  958. * @name renderTree
  959. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  960. * @description Creates an array of rows based on the tree, exporting only
  961. * the visible nodes and leaves
  962. *
  963. * @param {array} nodeList The list of nodes - can be grid.treeBase.tree, or can be node.children when
  964. * we're calling recursively
  965. * @returns {array} renderable rows
  966. */
  967. renderTree: function( nodeList ){
  968. var renderableRows = [];
  969. nodeList.forEach( function ( node ){
  970. if ( node.row.visible ){
  971. renderableRows.push( node.row );
  972. }
  973. if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
  974. renderableRows = renderableRows.concat( service.renderTree( node.children ) );
  975. }
  976. });
  977. return renderableRows;
  978. },
  979. /**
  980. * @ngdoc function
  981. * @name createTree
  982. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  983. * @description Creates a tree from the renderableRows
  984. *
  985. * @param {Grid} grid The grid
  986. * @param {array} renderableRows The rows we want to create a tree from
  987. * @returns {object} The tree we've build
  988. */
  989. createTree: function( grid, renderableRows ) {
  990. var currentLevel = -1;
  991. var parents = [];
  992. var currentState;
  993. grid.treeBase.tree = [];
  994. grid.treeBase.numberLevels = 0;
  995. var aggregations = service.getAggregations( grid );
  996. var createNode = function( row ){
  997. if ( typeof(row.entity.$$treeLevel) !== 'undefined' && row.treeLevel !== row.entity.$$treeLevel ){
  998. row.treeLevel = row.entity.$$treeLevel;
  999. }
  1000. if ( row.treeLevel <= currentLevel ){
  1001. // pop any levels that aren't parents of this level, formatting the aggregation at the same time
  1002. while ( row.treeLevel <= currentLevel ){
  1003. var lastParent = parents.pop();
  1004. service.finaliseAggregations( lastParent );
  1005. currentLevel--;
  1006. }
  1007. // reset our current state based on the new parent, set to expanded if this is a level 0 node
  1008. if ( parents.length > 0 ){
  1009. currentState = service.setCurrentState(parents);
  1010. } else {
  1011. currentState = uiGridTreeBaseConstants.EXPANDED;
  1012. }
  1013. }
  1014. // aggregate if this is a leaf node
  1015. if ( ( typeof(row.treeLevel) === 'undefined' || row.treeLevel === null || row.treeLevel < 0 ) && row.visible ){
  1016. service.aggregate( grid, row, parents );
  1017. }
  1018. // add this node to the tree
  1019. service.addOrUseNode(grid, row, parents, aggregations);
  1020. if ( typeof(row.treeLevel) !== 'undefined' && row.treeLevel !== null && row.treeLevel >= 0 ){
  1021. parents.push(row);
  1022. currentLevel++;
  1023. currentState = service.setCurrentState(parents);
  1024. }
  1025. // update the tree number of levels, so we can set header width if we need to
  1026. if ( grid.treeBase.numberLevels < row.treeLevel + 1){
  1027. grid.treeBase.numberLevels = row.treeLevel + 1;
  1028. }
  1029. };
  1030. renderableRows.forEach( createNode );
  1031. // finalise remaining aggregations
  1032. while ( parents.length > 0 ){
  1033. var lastParent = parents.pop();
  1034. service.finaliseAggregations( lastParent );
  1035. }
  1036. return grid.treeBase.tree;
  1037. },
  1038. /**
  1039. * @ngdoc function
  1040. * @name addOrUseNode
  1041. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  1042. * @description Creates a tree node for this row. If this row already has a treeNode
  1043. * recorded against it, preserves the state, but otherwise overwrites the data.
  1044. *
  1045. * @param {grid} grid The grid we're operating on
  1046. * @param {gridRow} row The row we want to set
  1047. * @param {array} parents An array of the parents this row should have
  1048. * @param {array} aggregationBase Empty aggregation information
  1049. * @returns {undefined} Updates the parents array, updates the row to have a treeNode, and updates the
  1050. * grid.treeBase.tree
  1051. */
  1052. addOrUseNode: function( grid, row, parents, aggregationBase ){
  1053. var newAggregations = [];
  1054. aggregationBase.forEach( function(aggregation){
  1055. newAggregations.push(service.buildAggregationObject(aggregation.col));
  1056. });
  1057. var newNode = { state: uiGridTreeBaseConstants.COLLAPSED, row: row, parentRow: null, aggregations: newAggregations, children: [] };
  1058. if ( row.treeNode ){
  1059. newNode.state = row.treeNode.state;
  1060. }
  1061. if ( parents.length > 0 ){
  1062. newNode.parentRow = parents[parents.length - 1];
  1063. }
  1064. row.treeNode = newNode;
  1065. if ( parents.length === 0 ){
  1066. grid.treeBase.tree.push( newNode );
  1067. } else {
  1068. parents[parents.length - 1].treeNode.children.push( newNode );
  1069. }
  1070. },
  1071. /**
  1072. * @ngdoc function
  1073. * @name setCurrentState
  1074. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  1075. * @description Looks at the parents array to determine our current state.
  1076. * If any node in the hierarchy is collapsed, then return collapsed, otherwise return
  1077. * expanded.
  1078. *
  1079. * @param {array} parents An array of the parents this row should have
  1080. * @returns {string} The state we should be setting to any nodes we see
  1081. */
  1082. setCurrentState: function( parents ){
  1083. var currentState = uiGridTreeBaseConstants.EXPANDED;
  1084. parents.forEach( function(parent){
  1085. if ( parent.treeNode.state === uiGridTreeBaseConstants.COLLAPSED ){
  1086. currentState = uiGridTreeBaseConstants.COLLAPSED;
  1087. }
  1088. });
  1089. return currentState;
  1090. },
  1091. /**
  1092. * @ngdoc function
  1093. * @name sortTree
  1094. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  1095. * @description Performs a recursive sort on the tree nodes, sorting the
  1096. * children of each node and putting them back into the children array.
  1097. *
  1098. * Before doing this it turns back on all the sortIgnore - things that were previously
  1099. * ignored we process now. Since we're sorting within the nodes, presumably anything
  1100. * that was already sorted is how we derived the nodes, we can keep those sorts too.
  1101. *
  1102. * We only sort tree nodes that are expanded - no point in wasting effort sorting collapsed
  1103. * nodes
  1104. *
  1105. * @param {Grid} grid The grid to get the aggregation information from
  1106. * @returns {array} The aggregation information
  1107. */
  1108. sortTree: function( grid ){
  1109. grid.columns.forEach( function( column ) {
  1110. if ( column.sort && column.sort.ignoreSort ){
  1111. delete column.sort.ignoreSort;
  1112. }
  1113. });
  1114. grid.treeBase.tree = service.sortInternal( grid, grid.treeBase.tree );
  1115. },
  1116. sortInternal: function( grid, treeList ){
  1117. var rows = treeList.map( function( node ){
  1118. return node.row;
  1119. });
  1120. rows = rowSorter.sort( grid, rows, grid.columns );
  1121. var treeNodes = rows.map( function( row ){
  1122. return row.treeNode;
  1123. });
  1124. treeNodes.forEach( function( node ){
  1125. if ( node.state === uiGridTreeBaseConstants.EXPANDED && node.children && node.children.length > 0 ){
  1126. node.children = service.sortInternal( grid, node.children );
  1127. }
  1128. });
  1129. return treeNodes;
  1130. },
  1131. /**
  1132. * @ngdoc function
  1133. * @name fixFilter
  1134. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  1135. * @description After filtering has run, we need to go back through the tree
  1136. * and make sure the parent rows are always visible if any of the child rows
  1137. * are visible (filtering may make a child visible, but the parent may not
  1138. * match the filter criteria)
  1139. *
  1140. * This has a risk of being computationally expensive, we do it by walking
  1141. * the tree and remembering whether there are any invisible nodes on the
  1142. * way down.
  1143. *
  1144. * @param {Grid} grid the grid to fix filters on
  1145. */
  1146. fixFilter: function( grid ){
  1147. var parentsVisible;
  1148. grid.treeBase.tree.forEach( function( node ){
  1149. if ( node.children && node.children.length > 0 ){
  1150. parentsVisible = node.row.visible;
  1151. service.fixFilterInternal( node.children, parentsVisible );
  1152. }
  1153. });
  1154. },
  1155. fixFilterInternal: function( nodes, parentsVisible) {
  1156. nodes.forEach( function( node ){
  1157. if ( node.row.visible && !parentsVisible ){
  1158. service.setParentsVisible( node );
  1159. parentsVisible = true;
  1160. }
  1161. if ( node.children && node.children.length > 0 ){
  1162. if ( service.fixFilterInternal( node.children, ( parentsVisible && node.row.visible ) ) ) {
  1163. parentsVisible = true;
  1164. }
  1165. }
  1166. });
  1167. return parentsVisible;
  1168. },
  1169. setParentsVisible: function( node ){
  1170. while ( node.parentRow ){
  1171. node.parentRow.visible = true;
  1172. node = node.parentRow.treeNode;
  1173. }
  1174. },
  1175. /**
  1176. * @ngdoc function
  1177. * @name buildAggregationObject
  1178. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  1179. * @description Build the object which is stored on the column for holding meta-data about the aggregation.
  1180. * This method should only be called with columns which have an aggregation.
  1181. *
  1182. * @param {GridColumn} column The column which this object relates to
  1183. * @returns {object} {col: GridColumn object, label: string, type: string (optional)}
  1184. */
  1185. buildAggregationObject: function( column ){
  1186. var newAggregation = { col: column };
  1187. if ( column.treeAggregation && column.treeAggregation.type ){
  1188. newAggregation.type = column.treeAggregation.type;
  1189. }
  1190. if ( column.treeAggregation && column.treeAggregation.label ){
  1191. newAggregation.label = column.treeAggregation.label;
  1192. }
  1193. return newAggregation;
  1194. },
  1195. /**
  1196. * @ngdoc function
  1197. * @name getAggregations
  1198. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  1199. * @description Looks through the grid columns to find those with aggregations,
  1200. * and collates the aggregation information into an array, returns that array
  1201. *
  1202. * @param {Grid} grid the grid to get the aggregation information from
  1203. * @returns {array} the aggregation information
  1204. */
  1205. getAggregations: function( grid ){
  1206. var aggregateArray = [];
  1207. grid.columns.forEach( function(column){
  1208. if ( typeof(column.treeAggregationFn) !== 'undefined' ){
  1209. aggregateArray.push( service.buildAggregationObject(column) );
  1210. if ( grid.options.showColumnFooter && typeof(column.colDef.aggregationType) === 'undefined' && column.treeAggregation ){
  1211. // Add aggregation object for footer
  1212. column.treeFooterAggregation = service.buildAggregationObject(column);
  1213. column.aggregationType = service.treeFooterAggregationType;
  1214. }
  1215. }
  1216. });
  1217. return aggregateArray;
  1218. },
  1219. /**
  1220. * @ngdoc function
  1221. * @name aggregate
  1222. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  1223. * @description Accumulate the data from this row onto the aggregations for each parent
  1224. *
  1225. * Iterate over the parents, then iterate over the aggregations for each of those parents,
  1226. * and perform the aggregation for each individual aggregation
  1227. *
  1228. * @param {Grid} grid grid object
  1229. * @param {GridRow} row the row we want to set grouping visibility on
  1230. * @param {array} parents the parents that we would want to aggregate onto
  1231. */
  1232. aggregate: function( grid, row, parents ){
  1233. if ( parents.length === 0 && row.treeNode && row.treeNode.aggregations ){
  1234. row.treeNode.aggregations.forEach(function(aggregation){
  1235. // Calculate aggregations for footer even if there are no grouped rows
  1236. if ( typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ) {
  1237. var fieldValue = grid.getCellValue(row, aggregation.col);
  1238. var numValue = Number(fieldValue);
  1239. aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
  1240. }
  1241. });
  1242. }
  1243. parents.forEach( function( parent, index ){
  1244. if ( parent.treeNode.aggregations ){
  1245. parent.treeNode.aggregations.forEach( function( aggregation ){
  1246. var fieldValue = grid.getCellValue(row, aggregation.col);
  1247. var numValue = Number(fieldValue);
  1248. aggregation.col.treeAggregationFn(aggregation, fieldValue, numValue, row);
  1249. if ( index === 0 && typeof(aggregation.col.treeFooterAggregation) !== 'undefined' ){
  1250. aggregation.col.treeAggregationFn(aggregation.col.treeFooterAggregation, fieldValue, numValue, row);
  1251. }
  1252. });
  1253. }
  1254. });
  1255. },
  1256. // Aggregation routines - no doco needed as self evident
  1257. nativeAggregations: function() {
  1258. var nativeAggregations = {
  1259. count: {
  1260. label: i18nService.get().aggregation.count,
  1261. menuTitle: i18nService.get().grouping.aggregate_count,
  1262. aggregationFn: function (aggregation, fieldValue, numValue) {
  1263. if (typeof(aggregation.value) === 'undefined') {
  1264. aggregation.value = 1;
  1265. } else {
  1266. aggregation.value++;
  1267. }
  1268. }
  1269. },
  1270. sum: {
  1271. label: i18nService.get().aggregation.sum,
  1272. menuTitle: i18nService.get().grouping.aggregate_sum,
  1273. aggregationFn: function( aggregation, fieldValue, numValue ) {
  1274. if (!isNaN(numValue)) {
  1275. if (typeof(aggregation.value) === 'undefined') {
  1276. aggregation.value = numValue;
  1277. } else {
  1278. aggregation.value += numValue;
  1279. }
  1280. }
  1281. }
  1282. },
  1283. min: {
  1284. label: i18nService.get().aggregation.min,
  1285. menuTitle: i18nService.get().grouping.aggregate_min,
  1286. aggregationFn: function( aggregation, fieldValue, numValue ) {
  1287. if (typeof(aggregation.value) === 'undefined') {
  1288. aggregation.value = fieldValue;
  1289. } else {
  1290. if (typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue < aggregation.value || aggregation.value === null)) {
  1291. aggregation.value = fieldValue;
  1292. }
  1293. }
  1294. }
  1295. },
  1296. max: {
  1297. label: i18nService.get().aggregation.max,
  1298. menuTitle: i18nService.get().grouping.aggregate_max,
  1299. aggregationFn: function( aggregation, fieldValue, numValue ){
  1300. if ( typeof(aggregation.value) === 'undefined' ){
  1301. aggregation.value = fieldValue;
  1302. } else {
  1303. if ( typeof(fieldValue) !== 'undefined' && fieldValue !== null && (fieldValue > aggregation.value || aggregation.value === null)){
  1304. aggregation.value = fieldValue;
  1305. }
  1306. }
  1307. }
  1308. },
  1309. avg: {
  1310. label: i18nService.get().aggregation.avg,
  1311. menuTitle: i18nService.get().grouping.aggregate_avg,
  1312. aggregationFn: function( aggregation, fieldValue, numValue ){
  1313. if ( typeof(aggregation.count) === 'undefined' ){
  1314. aggregation.count = 1;
  1315. } else {
  1316. aggregation.count++;
  1317. }
  1318. if ( isNaN(numValue) ){
  1319. return;
  1320. }
  1321. if ( typeof(aggregation.value) === 'undefined' || typeof(aggregation.sum) === 'undefined' ){
  1322. aggregation.value = numValue;
  1323. aggregation.sum = numValue;
  1324. } else {
  1325. aggregation.sum += numValue;
  1326. aggregation.value = aggregation.sum / aggregation.count;
  1327. }
  1328. }
  1329. }
  1330. };
  1331. return nativeAggregations;
  1332. },
  1333. /**
  1334. * @ngdoc function
  1335. * @name finaliseAggregation
  1336. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  1337. * @description Helper function used to finalize aggregation nodes and footer cells
  1338. *
  1339. * @param {gridRow} row The parent we're finalising
  1340. * @param {aggregation} aggregation The aggregation object manipulated by the aggregationFn
  1341. */
  1342. finaliseAggregation: function(row, aggregation){
  1343. if ( aggregation.col.treeAggregationUpdateEntity && typeof(row) !== 'undefined' && typeof(row.entity[ '$$' + aggregation.col.uid ]) !== 'undefined' ){
  1344. angular.extend( aggregation, row.entity[ '$$' + aggregation.col.uid ]);
  1345. }
  1346. if ( typeof(aggregation.col.treeAggregationFinalizerFn) === 'function' ){
  1347. aggregation.col.treeAggregationFinalizerFn( aggregation );
  1348. }
  1349. if ( typeof(aggregation.col.customTreeAggregationFinalizerFn) === 'function' ){
  1350. aggregation.col.customTreeAggregationFinalizerFn( aggregation );
  1351. }
  1352. if ( typeof(aggregation.rendered) === 'undefined' ){
  1353. aggregation.rendered = aggregation.label ? aggregation.label + aggregation.value : aggregation.value;
  1354. }
  1355. },
  1356. /**
  1357. * @ngdoc function
  1358. * @name finaliseAggregations
  1359. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  1360. * @description Format the data from the aggregation into the rendered text
  1361. * e.g. if we had label: 'sum: ' and value: 25, we'd create 'sum: 25'.
  1362. *
  1363. * As part of this we call any formatting callback routines we've been provided.
  1364. *
  1365. * We write our aggregation out to the row.entity if treeAggregationUpdateEntity is
  1366. * set on the column - we don't overwrite any information that's already there, we append
  1367. * to it so that grouping can have set the groupVal beforehand without us overwriting it.
  1368. *
  1369. * We need to copy the data from the row.entity first before we finalise the aggregation,
  1370. * we need that information for the finaliserFn
  1371. *
  1372. * @param {gridRow} row the parent we're finalising
  1373. */
  1374. finaliseAggregations: function( row ){
  1375. if ( row == null || typeof(row.treeNode.aggregations) === 'undefined' ){
  1376. return;
  1377. }
  1378. row.treeNode.aggregations.forEach( function( aggregation ) {
  1379. service.finaliseAggregation(row, aggregation);
  1380. if ( aggregation.col.treeAggregationUpdateEntity ){
  1381. var aggregationCopy = {};
  1382. angular.forEach( aggregation, function( value, key ){
  1383. if ( aggregation.hasOwnProperty(key) && key !== 'col' ){
  1384. aggregationCopy[key] = value;
  1385. }
  1386. });
  1387. row.entity[ '$$' + aggregation.col.uid ] = aggregationCopy;
  1388. }
  1389. });
  1390. },
  1391. /**
  1392. * @ngdoc function
  1393. * @name treeFooterAggregationType
  1394. * @methodOf ui.grid.treeBase.service:uiGridTreeBaseService
  1395. * @description Uses the tree aggregation functions and finalizers to set the
  1396. * column footer aggregations.
  1397. *
  1398. * @param {rows} rows The visible rows. not used, but accepted to match signature of GridColumn.aggregationType
  1399. * @param {GridColumn} column The column we are finalizing
  1400. */
  1401. treeFooterAggregationType: function( rows, column ) {
  1402. service.finaliseAggregation(undefined, column.treeFooterAggregation);
  1403. if ( typeof(column.treeFooterAggregation.value) === 'undefined' || column.treeFooterAggregation.rendered === null ){
  1404. // The was apparently no aggregation performed (perhaps this is a grouped column
  1405. return '';
  1406. }
  1407. return column.treeFooterAggregation.rendered;
  1408. }
  1409. };
  1410. return service;
  1411. }]);
  1412. /**
  1413. * @ngdoc directive
  1414. * @name ui.grid.treeBase.directive:uiGridTreeRowHeaderButtons
  1415. * @element div
  1416. *
  1417. * @description Provides the expand/collapse button on rows
  1418. */
  1419. module.directive('uiGridTreeBaseRowHeaderButtons', ['$templateCache', 'uiGridTreeBaseService',
  1420. function ($templateCache, uiGridTreeBaseService) {
  1421. return {
  1422. replace: true,
  1423. restrict: 'E',
  1424. template: $templateCache.get('ui-grid/treeBaseRowHeaderButtons'),
  1425. scope: true,
  1426. require: '^uiGrid',
  1427. link: function($scope, $elm, $attrs, uiGridCtrl) {
  1428. var self = uiGridCtrl.grid;
  1429. $scope.treeButtonClick = function(row, evt) {
  1430. evt.stopPropagation();
  1431. uiGridTreeBaseService.toggleRowTreeState(self, row, evt);
  1432. };
  1433. }
  1434. };
  1435. }]);
  1436. /**
  1437. * @ngdoc directive
  1438. * @name ui.grid.treeBase.directive:uiGridTreeBaseExpandAllButtons
  1439. * @element div
  1440. *
  1441. * @description Provides the expand/collapse all button
  1442. */
  1443. module.directive('uiGridTreeBaseExpandAllButtons', ['$templateCache', 'uiGridTreeBaseService',
  1444. function ($templateCache, uiGridTreeBaseService) {
  1445. return {
  1446. replace: true,
  1447. restrict: 'E',
  1448. template: $templateCache.get('ui-grid/treeBaseExpandAllButtons'),
  1449. scope: false,
  1450. link: function($scope, $elm, $attrs, uiGridCtrl) {
  1451. var self = $scope.col.grid;
  1452. $scope.headerButtonClick = function(row, evt) {
  1453. if ( self.treeBase.expandAll ){
  1454. uiGridTreeBaseService.collapseAllRows(self, evt);
  1455. } else {
  1456. uiGridTreeBaseService.expandAllRows(self, evt);
  1457. }
  1458. };
  1459. }
  1460. };
  1461. }]);
  1462. /**
  1463. * @ngdoc directive
  1464. * @name ui.grid.treeBase.directive:uiGridViewport
  1465. * @element div
  1466. *
  1467. * @description Stacks on top of ui.grid.uiGridViewport to set formatting on a tree header row
  1468. */
  1469. module.directive('uiGridViewport',
  1470. ['$compile', 'uiGridConstants', 'gridUtil', '$parse',
  1471. function ($compile, uiGridConstants, gridUtil, $parse) {
  1472. return {
  1473. priority: -200, // run after default directive
  1474. scope: false,
  1475. compile: function ($elm, $attrs) {
  1476. var rowRepeatDiv = angular.element($elm.children().children()[0]);
  1477. var existingNgClass = rowRepeatDiv.attr("ng-class");
  1478. var newNgClass = '';
  1479. if ( existingNgClass ) {
  1480. newNgClass = existingNgClass.slice(0, -1) + ",'ui-grid-tree-header-row': row.treeLevel > -1}";
  1481. } else {
  1482. newNgClass = "{'ui-grid-tree-header-row': row.treeLevel > -1}";
  1483. }
  1484. rowRepeatDiv.attr("ng-class", newNgClass);
  1485. return {
  1486. pre: function ($scope, $elm, $attrs, controllers) {
  1487. },
  1488. post: function ($scope, $elm, $attrs, controllers) {
  1489. }
  1490. };
  1491. }
  1492. };
  1493. }]);
  1494. })();