| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 part of app; | |
| 6 | |
| 7 abstract class TableTreeRow extends Observable { | |
| 8 final TableTreeRow parent; | |
| 9 @observable final int depth; | |
| 10 @observable final List<TableTreeRow> children = new List<TableTreeRow>(); | |
| 11 @observable final List<String> columns = []; | |
| 12 static const arrowRight = '\u2192'; | |
| 13 static const arrowDownRight = '\u21b3'; | |
| 14 static const showExpanderStyle = 'cursor: pointer;'; | |
| 15 static const hideExpanderStyle = 'visibility:hidden;'; | |
| 16 | |
| 17 // TODO(johnmccutchan): Move expander display decisions into html once | |
| 18 // tables and templates are better supported. | |
| 19 @observable String expander = arrowRight; | |
| 20 @observable String expanderStyle = showExpanderStyle; | |
| 21 | |
| 22 TableTreeRow(TableTreeRow parent) : | |
| 23 parent = parent, | |
| 24 depth = parent != null ? parent.depth+1 : 0 { | |
| 25 if (!hasChildren()) { | |
| 26 expanderStyle = hideExpanderStyle; | |
| 27 } | |
| 28 } | |
| 29 | |
| 30 bool _expanded = false; | |
| 31 bool get expanded => _expanded; | |
| 32 set expanded(bool expanded) { | |
| 33 var changed = _expanded != expanded; | |
| 34 _expanded = expanded; | |
| 35 if (changed) { | |
| 36 // If the state has changed, fire callbacks. | |
| 37 if (_expanded) { | |
| 38 expander = arrowDownRight; | |
| 39 onShow(); | |
| 40 } else { | |
| 41 expander = arrowRight; | |
| 42 onHide(); | |
| 43 } | |
| 44 } | |
| 45 } | |
| 46 | |
| 47 bool toggle() { | |
| 48 expanded = !expanded; | |
| 49 return expanded; | |
| 50 } | |
| 51 | |
| 52 bool hasChildren(); | |
| 53 | |
| 54 /// Fired when the tree row is expanded. Add children rows here. | |
| 55 void onShow(); | |
| 56 | |
| 57 /// Fired when the tree row is collapsed. | |
| 58 void onHide(); | |
| 59 } | |
| 60 | |
| 61 class TableTree extends Observable { | |
| 62 @observable final List<TableTreeRow> rows = toObservable([]); | |
| 63 | |
| 64 /// Create a table tree with column [headers]. | |
| 65 TableTree(); | |
| 66 | |
| 67 /// Initialize the table tree with the list of root children. | |
| 68 void initialize(TableTreeRow root) { | |
| 69 rows.clear(); | |
| 70 root.onShow(); | |
| 71 rows.addAll(root.children); | |
| 72 } | |
| 73 | |
| 74 /// Toggle expansion of row at [rowIndex]. | |
| 75 void toggle(int rowIndex) { | |
| 76 assert(rowIndex >= 0); | |
| 77 assert(rowIndex < rows.length); | |
| 78 var row = rows[rowIndex]; | |
| 79 if (row.toggle()) { | |
| 80 _expand(row); | |
| 81 } else { | |
| 82 _collapse(row); | |
| 83 } | |
| 84 } | |
| 85 | |
| 86 int _index(TableTreeRow row) => rows.indexOf(row); | |
| 87 | |
| 88 void _expand(TableTreeRow row) { | |
| 89 int index = _index(row); | |
| 90 assert(index != -1); | |
| 91 rows.insertAll(index + 1, row.children); | |
| 92 } | |
| 93 | |
| 94 void _collapse(TableTreeRow row) { | |
| 95 var childCount = row.children.length; | |
| 96 if (childCount == 0) { | |
| 97 return; | |
| 98 } | |
| 99 for (var i = 0; i < childCount; i++) { | |
| 100 // Close all inner rows. | |
| 101 if (row.children[i].expanded) { | |
| 102 _collapse(row.children[i]); | |
| 103 } | |
| 104 } | |
| 105 // Collapse this row. | |
| 106 row.expanded = false; | |
| 107 // Remove all children. | |
| 108 int index = _index(row); | |
| 109 rows.removeRange(index + 1, index + 1 + childCount); | |
| 110 } | |
| 111 } | |
| 112 | |
| 113 typedef String ValueFormatter(dynamic value); | |
| 114 | |
| 115 class SortedTableColumn { | |
| 116 static String toStringFormatter(dynamic v) { | |
| 117 return v != null ? v.toString() : '<null>'; | |
| 118 } | |
| 119 final String label; | |
| 120 final ValueFormatter formatter; | |
| 121 SortedTableColumn.withFormatter(this.label, this.formatter); | |
| 122 SortedTableColumn(this.label) | |
| 123 : formatter = toStringFormatter; | |
| 124 } | |
| 125 | |
| 126 class SortedTableRow { | |
| 127 final List values; | |
| 128 SortedTableRow(this.values); | |
| 129 } | |
| 130 | |
| 131 class SortedTable extends Observable { | |
| 132 final List<SortedTableColumn> columns; | |
| 133 final List<SortedTableRow> rows = new List<SortedTableRow>(); | |
| 134 final List<int> sortedRows = []; | |
| 135 | |
| 136 SortedTable(this.columns); | |
| 137 | |
| 138 int _sortColumnIndex = 0; | |
| 139 set sortColumnIndex(var index) { | |
| 140 assert(index >= 0); | |
| 141 assert(index < columns.length); | |
| 142 _sortColumnIndex = index; | |
| 143 notifyPropertyChange(#getColumnLabel, 0, 1); | |
| 144 } | |
| 145 int get sortColumnIndex => _sortColumnIndex; | |
| 146 bool _sortDescending = true; | |
| 147 bool get sortDescending => _sortDescending; | |
| 148 set sortDescending(var descending) { | |
| 149 _sortDescending = descending; | |
| 150 notifyPropertyChange(#getColumnLabel, 0, 1); | |
| 151 } | |
| 152 | |
| 153 | |
| 154 dynamic getSortKeyFor(int row, int col) { | |
| 155 return rows[row].values[col]; | |
| 156 } | |
| 157 | |
| 158 int _sortFuncDescending(int i, int j) { | |
| 159 var a = getSortKeyFor(i, _sortColumnIndex); | |
| 160 var b = getSortKeyFor(j, _sortColumnIndex); | |
| 161 return b.compareTo(a); | |
| 162 } | |
| 163 | |
| 164 int _sortFuncAscending(int i, int j) { | |
| 165 var a = getSortKeyFor(i, _sortColumnIndex); | |
| 166 var b = getSortKeyFor(j, _sortColumnIndex); | |
| 167 return a.compareTo(b); | |
| 168 } | |
| 169 | |
| 170 void sort() { | |
| 171 Stopwatch sw = new Stopwatch()..start(); | |
| 172 assert(_sortColumnIndex >= 0); | |
| 173 assert(_sortColumnIndex < columns.length); | |
| 174 if (_sortDescending) { | |
| 175 sortedRows.sort(_sortFuncDescending); | |
| 176 } else { | |
| 177 sortedRows.sort(_sortFuncAscending); | |
| 178 } | |
| 179 } | |
| 180 | |
| 181 void clearRows() { | |
| 182 rows.clear(); | |
| 183 sortedRows.clear(); | |
| 184 } | |
| 185 | |
| 186 void addRow(SortedTableRow row) { | |
| 187 sortedRows.add(rows.length); | |
| 188 rows.add(row); | |
| 189 } | |
| 190 | |
| 191 String getFormattedValue(int row, int column) { | |
| 192 var value = getValue(row, column); | |
| 193 var formatter = columns[column].formatter; | |
| 194 return formatter(value); | |
| 195 } | |
| 196 | |
| 197 @observable String getColumnLabel(int column) { | |
| 198 assert(column >= 0); | |
| 199 assert(column < columns.length); | |
| 200 // TODO(johnmccutchan): Move expander display decisions into html once | |
| 201 // tables and templates are better supported. | |
| 202 const arrowUp = '\u25BC'; | |
| 203 const arrowDown = '\u25B2'; | |
| 204 if (column != _sortColumnIndex) { | |
| 205 return columns[column].label + '\u2003'; | |
| 206 } | |
| 207 return columns[column].label + (_sortDescending ? arrowUp : arrowDown); | |
| 208 } | |
| 209 | |
| 210 dynamic getValue(int row, int column) { | |
| 211 return rows[row].values[column]; | |
| 212 } | |
| 213 } | |
| OLD | NEW |