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 |