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 { | 7 abstract class TableTreeRow extends Observable { |
8 static const arrowRight = '\u2192'; | 8 static const arrowRight = '\u2192'; |
9 static const arrowDownRight = '\u21b3'; | 9 static const arrowDownRight = '\u21b3'; |
10 // Number of pixels each subtree is indented. | 10 // Number of ems each subtree is indented. |
11 static const subtreeIndent = 16; | 11 static const subtreeIndent = 2; |
| 12 |
| 13 TableTreeRow(this.tree, TableTreeRow parent) : |
| 14 parent = parent, |
| 15 depth = parent != null ? parent.depth + 1 : 0 { |
| 16 } |
12 | 17 |
13 final TableTree tree; | 18 final TableTree tree; |
14 final TableTreeRow parent; | 19 final TableTreeRow parent; |
15 final int depth; | 20 final int depth; |
16 final List<TableTreeRow> children = new List<TableTreeRow>(); | 21 final List<TableTreeRow> children = new List<TableTreeRow>(); |
17 final List<TableCellElement> tableColumns = new List<TableCellElement>(); | 22 final List<TableCellElement> _tableColumns = new List<TableCellElement>(); |
| 23 final List<DivElement> flexColumns = new List<DivElement>(); |
18 SpanElement _expander; | 24 SpanElement _expander; |
19 TableRowElement _tr; | 25 TableRowElement _tr; |
20 TableRowElement get tr { | 26 TableRowElement get tr => _tr; |
21 assert(_tr != null); | |
22 return _tr; | |
23 } | |
24 | |
25 TableTreeRow(this.tree, TableTreeRow parent) : | |
26 parent = parent, | |
27 depth = parent != null ? parent.depth+1 : 0 { | |
28 } | |
29 | |
30 bool _expanded = false; | 27 bool _expanded = false; |
31 bool get expanded => _expanded; | 28 bool get expanded => _expanded; |
32 set expanded(bool expanded) { | 29 set expanded(bool expanded) { |
33 var changed = _expanded != expanded; | 30 var changed = _expanded != expanded; |
34 _expanded = expanded; | 31 _expanded = expanded; |
35 if (changed) { | 32 if (changed) { |
36 // If the state has changed, fire callbacks. | 33 // If the state has changed, fire callbacks. |
37 if (_expanded) { | 34 if (_expanded) { |
38 _onExpand(); | 35 _onExpand(); |
39 } else { | 36 } else { |
40 _onCollapse(); | 37 _onCollapse(); |
41 } | 38 } |
42 } | 39 } |
43 } | 40 } |
44 | 41 |
45 bool expandOrCollapse() { | 42 /// Fired when the tree row is being expanded. |
| 43 void _onExpand() { |
| 44 _updateExpanderView(); |
| 45 } |
| 46 |
| 47 /// Fired when the tree row is being collapsed. |
| 48 void _onCollapse() { |
| 49 for (var child in children) { |
| 50 child.onHide(); |
| 51 } |
| 52 _updateExpanderView(); |
| 53 } |
| 54 |
| 55 bool toggle() { |
46 expanded = !expanded; | 56 expanded = !expanded; |
47 return expanded; | 57 return expanded; |
48 } | 58 } |
49 | 59 |
50 bool hasChildren(); | 60 HtmlElement _makeColorBlock(String backgroundColor) { |
51 | 61 var colorBlock = new DivElement(); |
52 String _backgroundColorClassForRow() { | 62 colorBlock.style.minWidth = '2px'; |
53 const colors = const ['rowColor0', 'rowColor1', 'rowColor2', 'rowColor3', | 63 colorBlock.style.backgroundColor = backgroundColor; |
54 'rowColor4', 'rowColor5', 'rowColor6', 'rowColor7', | 64 return colorBlock; |
55 'rowColor8']; | |
56 var index = (depth - 1) % colors.length; | |
57 return colors[index]; | |
58 } | 65 } |
59 | 66 |
| 67 HtmlElement _makeExpander() { |
| 68 var expander = new SpanElement(); |
| 69 expander.style.minWidth = '1.5em'; |
| 70 expander.onClick.listen(onClick); |
| 71 return expander; |
| 72 } |
| 73 |
| 74 void onClick(Event e) { |
| 75 e.stopPropagation(); |
| 76 tree.toggle(this); |
| 77 } |
| 78 |
| 79 static const redColor = '#F44336'; |
| 80 static const blueColor = '#3F51B5'; |
| 81 static const purpleColor = '#673AB7'; |
| 82 static const greenColor = '#4CAF50'; |
| 83 static const orangeColor = '#FF9800'; |
| 84 static const lightGrayColor = '#FAFAFA'; |
| 85 |
60 void _buildRow() { | 86 void _buildRow() { |
| 87 const List backgroundColors = const [ |
| 88 purpleColor, |
| 89 redColor, |
| 90 greenColor, |
| 91 blueColor, |
| 92 orangeColor, |
| 93 ]; |
61 _tr = new TableRowElement(); | 94 _tr = new TableRowElement(); |
62 for (var i = 0; i < tree.columnCount; i++) { | 95 for (var i = 0; i < tree.columnCount; i++) { |
63 var cell = _tr.insertCell(-1); | 96 var cell = _tr.insertCell(-1); |
64 cell.classes.add(_backgroundColorClassForRow()); | 97 _tableColumns.add(cell); |
65 tableColumns.add(cell); | 98 var flex = new DivElement(); |
| 99 flex.classes.add('flex-row'); |
| 100 cell.children.add(flex); |
| 101 flexColumns.add(flex); |
66 } | 102 } |
67 var firstColumn = tableColumns[0]; | 103 var firstColumn = flexColumns[0]; |
68 var columnContainer = new DivElement(); | 104 _tableColumns[0].style.paddingLeft = '${(depth - 1) * subtreeIndent}em'; |
69 columnContainer.classes.add('flex-row'); | 105 var backgroundColor = lightGrayColor; |
70 _expander = new SpanElement(); | 106 if (depth > 1) { |
71 _expander.style.display = 'inline-block'; | 107 var colorIndex = (depth - 1) % backgroundColors.length; |
72 _expander.style.display = 'inline-block'; | 108 backgroundColor = backgroundColors[colorIndex]; |
73 _expander.style.minWidth = '1.5em'; | 109 } |
74 _expander.onClick.listen(onClick); | 110 var colorBlock = _makeColorBlock(backgroundColor); |
75 columnContainer.children.add(_expander); | 111 firstColumn.children.add(colorBlock); |
76 firstColumn.style.paddingLeft = '${depth * subtreeIndent}px'; | 112 _expander = _makeExpander(); |
77 firstColumn.children.add(columnContainer); | 113 firstColumn.children.add(_expander); |
78 updateExpanderView(); | 114 // Enable expansion by clicking anywhere on the first column. |
| 115 firstColumn.onClick.listen(onClick); |
| 116 _updateExpanderView(); |
79 } | 117 } |
80 | 118 |
81 void updateExpanderView() { | 119 void _updateExpanderView() { |
82 if (_expander == null) { | 120 if (_expander == null) { |
83 return; | 121 return; |
84 } | 122 } |
85 if (!hasChildren()) { | 123 if (!hasChildren()) { |
86 _expander.style.visibility = 'hidden'; | 124 _expander.style.visibility = 'hidden'; |
87 _expander.style.cursor = 'auto'; | 125 _expander.classes.remove('pointer'); |
88 return; | 126 return; |
89 } else { | 127 } else { |
90 _expander.style.visibility = 'visible'; | 128 _expander.style.visibility = 'visible'; |
91 _expander.style.cursor = 'pointer'; | 129 _expander.classes.add('pointer'); |
92 } | 130 } |
93 _expander.text = expanded ? arrowDownRight : arrowRight; | 131 _expander.children.clear(); |
| 132 _expander.children.add(expanded ? |
| 133 new Element.tag('icon-expand-more') : |
| 134 new Element.tag('icon-chevron-right')); |
94 } | 135 } |
95 | 136 |
| 137 bool hasChildren(); |
| 138 |
96 /// Fired when the tree row is being shown. | 139 /// Fired when the tree row is being shown. |
97 /// Populate tr and add logical children here. | 140 /// Populate tr and add logical children here. |
98 void onShow() { | 141 void onShow() { |
99 assert(_tr == null); | 142 assert(_tr == null); |
100 _buildRow(); | 143 _buildRow(); |
101 } | 144 } |
102 | 145 |
103 /// Fired when the tree row is being hidden. | 146 /// Fired when the tree row is being hidden. |
104 void onHide() { | 147 void onHide() { |
105 assert(_tr != null); | |
106 _tr = null; | 148 _tr = null; |
107 tableColumns.clear(); | |
108 _expander = null; | 149 _expander = null; |
109 } | 150 if (_tableColumns != null) { |
110 | 151 _tableColumns.clear(); |
111 /// Fired when the tree row is being expanded. | |
112 void _onExpand() { | |
113 for (var child in children) { | |
114 child.onShow(); | |
115 child.updateExpanderView(); | |
116 } | 152 } |
117 updateExpanderView(); | 153 if (flexColumns != null) { |
118 } | 154 flexColumns.clear(); |
119 | |
120 /// Fired when the tree row is being collapsed. | |
121 void _onCollapse() { | |
122 for (var child in children) { | |
123 child.onHide(); | |
124 } | 155 } |
125 updateExpanderView(); | |
126 } | |
127 | |
128 void onClick(Event e) { | |
129 tree.toggle(this); | |
130 e.stopPropagation(); | |
131 } | 156 } |
132 } | 157 } |
133 | 158 |
134 class TableTree extends Observable { | 159 class TableTree extends Observable { |
135 final TableSectionElement tableBody; | 160 final TableSectionElement tableBody; |
136 final List<TableTreeRow> rows = []; | 161 final List<TableTreeRow> rows = []; |
137 final int columnCount; | 162 final int columnCount; |
138 | 163 Future _pendingOperation; |
139 /// Create a table tree with column [headers]. | 164 /// Create a table tree with column [headers]. |
140 TableTree(this.tableBody, this.columnCount); | 165 TableTree(this.tableBody, this.columnCount); |
141 | 166 |
142 void clear() { | 167 void clear() { |
143 tableBody.children.clear(); | 168 tableBody.children.clear(); |
144 rows.clear(); | 169 rows.clear(); |
145 } | 170 } |
146 | 171 |
147 /// Initialize the table tree with the list of root children. | 172 /// Initialize the table tree with the list of root children. |
148 void initialize(TableTreeRow root) { | 173 void initialize(TableTreeRow root) { |
149 clear(); | 174 clear(); |
150 root.onShow(); | 175 root.onShow(); |
151 rows.addAll(root.children); | 176 toggle(root); |
152 for (var i = 0; i < rows.length; i++) { | |
153 rows[i].onShow(); | |
154 tableBody.children.add(rows[i].tr); | |
155 } | |
156 } | 177 } |
157 | 178 |
158 /// Toggle expansion of row in tree. | 179 /// Toggle expansion of row in tree. |
159 void toggle(TableTreeRow row) { | 180 toggle(TableTreeRow row) async { |
160 if (row.expandOrCollapse()) { | 181 if (_pendingOperation != null) { |
161 _expand(row); | 182 return; |
| 183 } |
| 184 if (row.toggle()) { |
| 185 document.body.classes.add('busy'); |
| 186 _pendingOperation = _expand(row); |
| 187 await _pendingOperation; |
| 188 _pendingOperation = null; |
| 189 document.body.classes.remove('busy'); |
| 190 if (row.children.length == 1) { |
| 191 // Auto expand single child. |
| 192 await toggle(row.children[0]); |
| 193 } |
162 } else { | 194 } else { |
163 _collapse(row); | 195 document.body.classes.add('busy'); |
| 196 _pendingOperation = _collapse(row); |
| 197 await _pendingOperation; |
| 198 _pendingOperation = null; |
| 199 document.body.classes.remove('busy'); |
164 } | 200 } |
165 } | 201 } |
166 | 202 |
167 int _index(TableTreeRow row) => rows.indexOf(row); | 203 int _index(TableTreeRow row) => rows.indexOf(row); |
168 | 204 |
169 void _expand(TableTreeRow row) { | 205 _insertRow(index, child) { |
| 206 rows.insert(index, child); |
| 207 tableBody.children.insert(index, child.tr); |
| 208 } |
| 209 |
| 210 _expand(TableTreeRow row) async { |
170 int index = _index(row); | 211 int index = _index(row); |
171 assert(index != -1); | 212 if ((index == -1) && (rows.length != 0)) { |
172 rows.insertAll(index + 1, row.children); | 213 return; |
173 for (var i = 0; i < row.children.length; i++) { | 214 } |
174 tableBody.children.insert(index + i + 1, row.children[i].tr); | 215 assert((index != -1) || (rows.length == 0)); |
| 216 var i = 0; |
| 217 var addPerIteration = 10; |
| 218 while (i < row.children.length) { |
| 219 await window.animationFrame; |
| 220 for (var j = 0; j < addPerIteration; j++) { |
| 221 if (i == row.children.length) { |
| 222 break; |
| 223 } |
| 224 var child = row.children[i]; |
| 225 child.onShow(); |
| 226 child._updateExpanderView(); |
| 227 _insertRow(index + i + 1, child); |
| 228 i++; |
| 229 } |
175 } | 230 } |
176 } | 231 } |
177 | 232 |
178 void _collapse(TableTreeRow row) { | 233 _collapseSync(TableTreeRow row) { |
179 var childCount = row.children.length; | 234 var childCount = row.children.length; |
180 if (childCount == 0) { | 235 if (childCount == 0) { |
181 return; | 236 return; |
182 } | 237 } |
183 for (var i = 0; i < childCount; i++) { | 238 for (var i = 0; i < childCount; i++) { |
184 // Close all inner rows. | 239 // Close all inner rows. |
185 if (row.children[i].expanded) { | 240 if (row.children[i].expanded) { |
186 _collapse(row.children[i]); | 241 _collapseSync(row.children[i]); |
187 } | 242 } |
188 } | 243 } |
189 // Collapse this row. | 244 // Collapse this row. |
190 row.expanded = false; | 245 row.expanded = false; |
191 // Remove all children. | 246 // Remove all children. |
192 int index = _index(row); | 247 int index = _index(row); |
193 rows.removeRange(index + 1, index + 1 + childCount); | |
194 for (var i = 0; i < childCount; i++) { | 248 for (var i = 0; i < childCount; i++) { |
| 249 rows.removeAt(index + 1); |
195 tableBody.children.removeAt(index + 1); | 250 tableBody.children.removeAt(index + 1); |
196 } | 251 } |
197 } | 252 } |
| 253 |
| 254 _collapse(TableTreeRow row) async { |
| 255 _collapseSync(row); |
| 256 } |
198 } | 257 } |
199 | 258 |
200 typedef String ValueFormatter(dynamic value); | 259 typedef String ValueFormatter(dynamic value); |
201 | 260 |
202 class SortedTableColumn { | 261 class SortedTableColumn { |
203 static String toStringFormatter(dynamic v) { | 262 static String toStringFormatter(dynamic v) { |
204 return v != null ? v.toString() : '<null>'; | 263 return v != null ? v.toString() : '<null>'; |
205 } | 264 } |
206 final String label; | 265 final String label; |
207 final ValueFormatter formatter; | 266 final ValueFormatter formatter; |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
290 if (column != _sortColumnIndex) { | 349 if (column != _sortColumnIndex) { |
291 return columns[column].label + '\u2003'; | 350 return columns[column].label + '\u2003'; |
292 } | 351 } |
293 return columns[column].label + (_sortDescending ? arrowUp : arrowDown); | 352 return columns[column].label + (_sortDescending ? arrowUp : arrowDown); |
294 } | 353 } |
295 | 354 |
296 dynamic getValue(int row, int column) { | 355 dynamic getValue(int row, int column) { |
297 return rows[row].values[column]; | 356 return rows[row].values[column]; |
298 } | 357 } |
299 } | 358 } |
OLD | NEW |