| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of app; | 5 part of app; |
| 6 | 6 |
| 7 abstract class TableTreeRow extends Observable { | |
| 8 static const arrowRight = '\u2192'; | |
| 9 static const arrowDownRight = '\u21b3'; | |
| 10 // Number of ems each subtree is indented. | |
| 11 static const subtreeIndent = 2; | |
| 12 | |
| 13 TableTreeRow(this.tree, TableTreeRow parent) : | |
| 14 parent = parent, | |
| 15 depth = parent != null ? parent.depth + 1 : 0 { | |
| 16 } | |
| 17 | |
| 18 final TableTree tree; | |
| 19 final TableTreeRow parent; | |
| 20 final int depth; | |
| 21 final List<TableTreeRow> children = new List<TableTreeRow>(); | |
| 22 final List<TableCellElement> _tableColumns = new List<TableCellElement>(); | |
| 23 final List<DivElement> flexColumns = new List<DivElement>(); | |
| 24 final List<StreamSubscription> listeners = new List<StreamSubscription>(); | |
| 25 | |
| 26 SpanElement _expander; | |
| 27 TableRowElement _tr; | |
| 28 TableRowElement get tr => _tr; | |
| 29 bool _expanded = false; | |
| 30 bool get expanded => _expanded; | |
| 31 set expanded(bool expanded) { | |
| 32 var changed = _expanded != expanded; | |
| 33 _expanded = expanded; | |
| 34 if (changed) { | |
| 35 // If the state has changed, fire callbacks. | |
| 36 if (_expanded) { | |
| 37 _onExpand(); | |
| 38 } else { | |
| 39 _onCollapse(); | |
| 40 } | |
| 41 } | |
| 42 } | |
| 43 | |
| 44 /// Fired when the tree row is being expanded. | |
| 45 void _onExpand() { | |
| 46 _updateExpanderView(); | |
| 47 } | |
| 48 | |
| 49 /// Fired when the tree row is being collapsed. | |
| 50 void _onCollapse() { | |
| 51 for (var child in children) { | |
| 52 child.onHide(); | |
| 53 } | |
| 54 _updateExpanderView(); | |
| 55 } | |
| 56 | |
| 57 bool toggle() { | |
| 58 expanded = !expanded; | |
| 59 return expanded; | |
| 60 } | |
| 61 | |
| 62 HtmlElement _makeColorBlock(String backgroundColor) { | |
| 63 var colorBlock = new DivElement(); | |
| 64 colorBlock.style.minWidth = '2px'; | |
| 65 colorBlock.style.backgroundColor = backgroundColor; | |
| 66 return colorBlock; | |
| 67 } | |
| 68 | |
| 69 HtmlElement _makeExpander() { | |
| 70 var expander = new SpanElement(); | |
| 71 expander.style.minWidth = '24px'; | |
| 72 expander.style.minHeight = '24px'; | |
| 73 listeners.add(expander.onClick.listen(onClick)); | |
| 74 return expander; | |
| 75 } | |
| 76 | |
| 77 void _cleanUpListeners() { | |
| 78 for (var i = 0; i < listeners.length; i++) { | |
| 79 listeners[i].cancel(); | |
| 80 } | |
| 81 listeners.clear(); | |
| 82 } | |
| 83 | |
| 84 void onClick(Event e) { | |
| 85 e.stopPropagation(); | |
| 86 tree.toggle(this); | |
| 87 } | |
| 88 | |
| 89 static const redColor = '#F44336'; | |
| 90 static const blueColor = '#3F51B5'; | |
| 91 static const purpleColor = '#673AB7'; | |
| 92 static const greenColor = '#4CAF50'; | |
| 93 static const orangeColor = '#FF9800'; | |
| 94 static const lightGrayColor = '#FAFAFA'; | |
| 95 | |
| 96 void _buildRow() { | |
| 97 const List backgroundColors = const [ | |
| 98 purpleColor, | |
| 99 redColor, | |
| 100 greenColor, | |
| 101 blueColor, | |
| 102 orangeColor, | |
| 103 ]; | |
| 104 _tr = new TableRowElement(); | |
| 105 for (var i = 0; i < tree.columnCount; i++) { | |
| 106 var cell = _tr.insertCell(-1); | |
| 107 _tableColumns.add(cell); | |
| 108 var flex = new DivElement(); | |
| 109 flex.classes.add('flex-row'); | |
| 110 cell.children.add(flex); | |
| 111 flexColumns.add(flex); | |
| 112 } | |
| 113 var firstColumn = flexColumns[0]; | |
| 114 _tableColumns[0].style.paddingLeft = '${(depth - 1) * subtreeIndent}em'; | |
| 115 var backgroundColor = lightGrayColor; | |
| 116 if (depth > 1) { | |
| 117 var colorIndex = (depth - 1) % backgroundColors.length; | |
| 118 backgroundColor = backgroundColors[colorIndex]; | |
| 119 } | |
| 120 var colorBlock = _makeColorBlock(backgroundColor); | |
| 121 firstColumn.children.add(colorBlock); | |
| 122 _expander = _makeExpander(); | |
| 123 firstColumn.children.add(_expander); | |
| 124 // Enable expansion by clicking anywhere on the first column. | |
| 125 listeners.add(firstColumn.onClick.listen(onClick)); | |
| 126 _updateExpanderView(); | |
| 127 } | |
| 128 | |
| 129 void _updateExpanderView() { | |
| 130 if (_expander == null) { | |
| 131 return; | |
| 132 } | |
| 133 if (!hasChildren()) { | |
| 134 _expander.style.visibility = 'hidden'; | |
| 135 _expander.classes.remove('pointer'); | |
| 136 return; | |
| 137 } else { | |
| 138 _expander.style.visibility = 'visible'; | |
| 139 _expander.classes.add('pointer'); | |
| 140 } | |
| 141 _expander.children.clear(); | |
| 142 _expander.children.add(expanded ? | |
| 143 new Element.tag('icon-expand-more') : | |
| 144 new Element.tag('icon-chevron-right')); | |
| 145 } | |
| 146 | |
| 147 bool hasChildren(); | |
| 148 | |
| 149 /// Fired when the tree row is being shown. | |
| 150 /// Populate tr and add logical children here. | |
| 151 void onShow() { | |
| 152 assert(_tr == null); | |
| 153 _buildRow(); | |
| 154 } | |
| 155 | |
| 156 /// Fired when the tree row is being hidden. | |
| 157 void onHide() { | |
| 158 _tr = null; | |
| 159 _expander = null; | |
| 160 if (_tableColumns != null) { | |
| 161 _tableColumns.clear(); | |
| 162 } | |
| 163 if (flexColumns != null) { | |
| 164 flexColumns.clear(); | |
| 165 } | |
| 166 _cleanUpListeners(); | |
| 167 } | |
| 168 } | |
| 169 | |
| 170 class TableTree extends Observable { | |
| 171 final TableSectionElement tableBody; | |
| 172 final List<TableTreeRow> rows = []; | |
| 173 final int columnCount; | |
| 174 Future _pendingOperation; | |
| 175 /// Create a table tree with column [headers]. | |
| 176 TableTree(this.tableBody, this.columnCount); | |
| 177 | |
| 178 void clear() { | |
| 179 tableBody.children.clear(); | |
| 180 for (var i = 0; i < rows.length; i++) { | |
| 181 rows[i]._cleanUpListeners(); | |
| 182 } | |
| 183 rows.clear(); | |
| 184 } | |
| 185 | |
| 186 /// Initialize the table tree with the list of root children. | |
| 187 void initialize(TableTreeRow root) { | |
| 188 clear(); | |
| 189 root.onShow(); | |
| 190 toggle(root); | |
| 191 } | |
| 192 | |
| 193 /// Toggle expansion of row in tree. | |
| 194 toggle(TableTreeRow row) async { | |
| 195 if (_pendingOperation != null) { | |
| 196 return; | |
| 197 } | |
| 198 if (row.toggle()) { | |
| 199 document.body.classes.add('busy'); | |
| 200 _pendingOperation = _expand(row); | |
| 201 await _pendingOperation; | |
| 202 _pendingOperation = null; | |
| 203 document.body.classes.remove('busy'); | |
| 204 if (row.children.length == 1) { | |
| 205 // Auto expand single child. | |
| 206 await toggle(row.children[0]); | |
| 207 } | |
| 208 } else { | |
| 209 document.body.classes.add('busy'); | |
| 210 _pendingOperation = _collapse(row); | |
| 211 await _pendingOperation; | |
| 212 _pendingOperation = null; | |
| 213 document.body.classes.remove('busy'); | |
| 214 } | |
| 215 } | |
| 216 | |
| 217 int _index(TableTreeRow row) => rows.indexOf(row); | |
| 218 | |
| 219 _insertRow(index, child) { | |
| 220 rows.insert(index, child); | |
| 221 tableBody.children.insert(index, child.tr); | |
| 222 } | |
| 223 | |
| 224 _expand(TableTreeRow row) async { | |
| 225 int index = _index(row); | |
| 226 if ((index == -1) && (rows.length != 0)) { | |
| 227 return; | |
| 228 } | |
| 229 assert((index != -1) || (rows.length == 0)); | |
| 230 var i = 0; | |
| 231 var addPerIteration = 10; | |
| 232 while (i < row.children.length) { | |
| 233 await window.animationFrame; | |
| 234 for (var j = 0; j < addPerIteration; j++) { | |
| 235 if (i == row.children.length) { | |
| 236 break; | |
| 237 } | |
| 238 var child = row.children[i]; | |
| 239 child.onShow(); | |
| 240 child._updateExpanderView(); | |
| 241 _insertRow(index + i + 1, child); | |
| 242 i++; | |
| 243 } | |
| 244 } | |
| 245 } | |
| 246 | |
| 247 _collapseSync(TableTreeRow row) { | |
| 248 var childCount = row.children.length; | |
| 249 if (childCount == 0) { | |
| 250 return; | |
| 251 } | |
| 252 for (var i = 0; i < childCount; i++) { | |
| 253 // Close all inner rows. | |
| 254 if (row.children[i].expanded) { | |
| 255 _collapseSync(row.children[i]); | |
| 256 } | |
| 257 } | |
| 258 // Collapse this row. | |
| 259 row.expanded = false; | |
| 260 // Remove all children. | |
| 261 int index = _index(row); | |
| 262 for (var i = 0; i < childCount; i++) { | |
| 263 rows.removeAt(index + 1); | |
| 264 tableBody.children.removeAt(index + 1); | |
| 265 } | |
| 266 } | |
| 267 | |
| 268 _collapse(TableTreeRow row) async { | |
| 269 _collapseSync(row); | |
| 270 } | |
| 271 } | |
| 272 | |
| 273 typedef String ValueFormatter(dynamic value); | 7 typedef String ValueFormatter(dynamic value); |
| 274 | 8 |
| 275 class SortedTableColumn { | 9 class SortedTableColumn { |
| 276 static String toStringFormatter(dynamic v) { | 10 static String toStringFormatter(dynamic v) { |
| 277 return v != null ? v.toString() : '<null>'; | 11 return v != null ? v.toString() : '<null>'; |
| 278 } | 12 } |
| 279 final String label; | 13 final String label; |
| 280 final ValueFormatter formatter; | 14 final ValueFormatter formatter; |
| 281 SortedTableColumn.withFormatter(this.label, this.formatter); | 15 SortedTableColumn.withFormatter(this.label, this.formatter); |
| 282 SortedTableColumn(this.label) | 16 SortedTableColumn(this.label) |
| 283 : formatter = toStringFormatter; | 17 : formatter = toStringFormatter; |
| 284 } | 18 } |
| 285 | 19 |
| 286 class SortedTableRow { | 20 class SortedTableRow { |
| 287 final List values; | 21 final List values; |
| 288 SortedTableRow(this.values); | 22 SortedTableRow(this.values); |
| 289 } | 23 } |
| 290 | 24 |
| 291 class SortedTable extends Observable { | 25 class SortedTable { |
| 292 final List<SortedTableColumn> columns; | 26 final List<SortedTableColumn> columns; |
| 293 final List<SortedTableRow> rows = new List<SortedTableRow>(); | 27 final List<SortedTableRow> rows = new List<SortedTableRow>(); |
| 294 final List<int> sortedRows = []; | 28 final List<int> sortedRows = []; |
| 295 | 29 |
| 296 SortedTable(this.columns); | 30 SortedTable(this.columns); |
| 297 | 31 |
| 298 int _sortColumnIndex = 0; | 32 int _sortColumnIndex = 0; |
| 299 set sortColumnIndex(var index) { | 33 set sortColumnIndex(var index) { |
| 300 assert(index >= 0); | 34 assert(index >= 0); |
| 301 assert(index < columns.length); | 35 assert(index < columns.length); |
| 302 _sortColumnIndex = index; | 36 _sortColumnIndex = index; |
| 303 notifyPropertyChange(#getColumnLabel, 0, 1); | |
| 304 } | 37 } |
| 305 int get sortColumnIndex => _sortColumnIndex; | 38 int get sortColumnIndex => _sortColumnIndex; |
| 306 bool _sortDescending = true; | 39 bool _sortDescending = true; |
| 307 bool get sortDescending => _sortDescending; | 40 bool get sortDescending => _sortDescending; |
| 308 set sortDescending(var descending) { | 41 set sortDescending(var descending) { |
| 309 _sortDescending = descending; | 42 _sortDescending = descending; |
| 310 notifyPropertyChange(#getColumnLabel, 0, 1); | |
| 311 } | 43 } |
| 312 | 44 |
| 313 | 45 |
| 314 dynamic getSortKeyFor(int row, int col) { | 46 dynamic getSortKeyFor(int row, int col) { |
| 315 return rows[row].values[col]; | 47 return rows[row].values[col]; |
| 316 } | 48 } |
| 317 | 49 |
| 318 int _sortFuncDescending(int i, int j) { | 50 int _sortFuncDescending(int i, int j) { |
| 319 var a = getSortKeyFor(i, _sortColumnIndex); | 51 var a = getSortKeyFor(i, _sortColumnIndex); |
| 320 var b = getSortKeyFor(j, _sortColumnIndex); | 52 var b = getSortKeyFor(j, _sortColumnIndex); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 346 sortedRows.add(rows.length); | 78 sortedRows.add(rows.length); |
| 347 rows.add(row); | 79 rows.add(row); |
| 348 } | 80 } |
| 349 | 81 |
| 350 String getFormattedValue(int row, int column) { | 82 String getFormattedValue(int row, int column) { |
| 351 var value = getValue(row, column); | 83 var value = getValue(row, column); |
| 352 var formatter = columns[column].formatter; | 84 var formatter = columns[column].formatter; |
| 353 return formatter(value); | 85 return formatter(value); |
| 354 } | 86 } |
| 355 | 87 |
| 356 @observable String getColumnLabel(int column) { | 88 String getColumnLabel(int column) { |
| 357 assert(column >= 0); | 89 assert(column >= 0); |
| 358 assert(column < columns.length); | 90 assert(column < columns.length); |
| 359 // TODO(johnmccutchan): Move expander display decisions into html once | 91 // TODO(johnmccutchan): Move expander display decisions into html once |
| 360 // tables and templates are better supported. | 92 // tables and templates are better supported. |
| 361 const arrowUp = '\u25BC'; | 93 const arrowUp = '\u25BC'; |
| 362 const arrowDown = '\u25B2'; | 94 const arrowDown = '\u25B2'; |
| 363 if (column != _sortColumnIndex) { | 95 if (column != _sortColumnIndex) { |
| 364 return columns[column].label + '\u2003'; | 96 return columns[column].label + '\u2003'; |
| 365 } | 97 } |
| 366 return columns[column].label + (_sortDescending ? arrowUp : arrowDown); | 98 return columns[column].label + (_sortDescending ? arrowUp : arrowDown); |
| 367 } | 99 } |
| 368 | 100 |
| 369 dynamic getValue(int row, int column) { | 101 dynamic getValue(int row, int column) { |
| 370 return rows[row].values[column]; | 102 return rows[row].values[column]; |
| 371 } | 103 } |
| 372 } | 104 } |
| OLD | NEW |