Simon Hunt
Committed by Gerrit Code Review

ONOS-3780: Table model now handles two column sorts.

Change-Id: I8899d56fdca2084e4a7ca0392c21d14f1bc6ea62
...@@ -52,6 +52,7 @@ public class TableModel { ...@@ -52,6 +52,7 @@ public class TableModel {
52 52
53 private static final CellComparator DEF_CMP = DefaultCellComparator.INSTANCE; 53 private static final CellComparator DEF_CMP = DefaultCellComparator.INSTANCE;
54 private static final CellFormatter DEF_FMT = DefaultCellFormatter.INSTANCE; 54 private static final CellFormatter DEF_FMT = DefaultCellFormatter.INSTANCE;
55 + private static final String EMPTY = "";
55 56
56 private final String[] columnIds; 57 private final String[] columnIds;
57 private final Set<String> idSet; 58 private final Set<String> idSet;
...@@ -206,14 +207,17 @@ public class TableModel { ...@@ -206,14 +207,17 @@ public class TableModel {
206 } 207 }
207 208
208 /** 209 /**
209 - * Sorts the table rows based on the specified column, in the 210 + * Sorts the table rows based on the specified columns, in the
210 - * specified direction. 211 + * specified directions. The second column is optional, and can be
212 + * disregarded by passing null into id2 and dir2.
211 * 213 *
212 - * @param columnId column identifier 214 + * @param id1 first column identifier
213 - * @param dir sort direction 215 + * @param dir1 first column sort direction
216 + * @param id2 second column identifier (may be null)
217 + * @param dir2 second column sort direction (may be null)
214 */ 218 */
215 - public void sort(String columnId, SortDir dir) { 219 + public void sort(String id1, SortDir dir1, String id2, SortDir dir2) {
216 - Collections.sort(rows, new RowComparator(columnId, dir)); 220 + Collections.sort(rows, new RowComparator(id1, dir1, id2, dir2));
217 } 221 }
218 222
219 223
...@@ -225,33 +229,54 @@ public class TableModel { ...@@ -225,33 +229,54 @@ public class TableModel {
225 DESC 229 DESC
226 } 230 }
227 231
232 + private boolean nullOrEmpty(String s) {
233 + return s == null || EMPTY.equals(s.trim());
234 + }
235 +
228 /** 236 /**
229 * Row comparator. 237 * Row comparator.
230 */ 238 */
231 private class RowComparator implements Comparator<Row> { 239 private class RowComparator implements Comparator<Row> {
232 - private final String columnId; 240 + private final String id1;
233 - private final SortDir dir; 241 + private final SortDir dir1;
234 - private final CellComparator cellComparator; 242 + private final String id2;
243 + private final SortDir dir2;
244 + private final CellComparator cc1;
245 + private final CellComparator cc2;
235 246
236 /** 247 /**
237 * Constructs a row comparator based on the specified 248 * Constructs a row comparator based on the specified
238 - * column identifier and sort direction. 249 + * column identifiers and sort directions. Note that id2 and dir2 may
250 + * be null.
239 * 251 *
240 - * @param columnId column identifier 252 + * @param id1 first column identifier
241 - * @param dir sort direction 253 + * @param dir1 first column sort direction
254 + * @param id2 second column identifier
255 + * @param dir2 second column sort direction
242 */ 256 */
243 - public RowComparator(String columnId, SortDir dir) { 257 + public RowComparator(String id1, SortDir dir1, String id2, SortDir dir2) {
244 - this.columnId = columnId; 258 + this.id1 = id1;
245 - this.dir = dir; 259 + this.dir1 = dir1;
246 - cellComparator = getComparator(columnId); 260 + this.id2 = id2;
261 + this.dir2 = dir2;
262 + cc1 = getComparator(id1);
263 + cc2 = nullOrEmpty(id2) ? null : getComparator(id2);
247 } 264 }
248 265
249 @Override 266 @Override
250 public int compare(Row a, Row b) { 267 public int compare(Row a, Row b) {
251 - Object cellA = a.get(columnId); 268 + Object cellA = a.get(id1);
252 - Object cellB = b.get(columnId); 269 + Object cellB = b.get(id1);
253 - int result = cellComparator.compare(cellA, cellB); 270 + int result = cc1.compare(cellA, cellB);
254 - return dir == SortDir.ASC ? result : -result; 271 + result = dir1 == SortDir.ASC ? result : -result;
272 +
273 + if (result == 0 && cc2 != null) {
274 + cellA = a.get(id2);
275 + cellB = b.get(id2);
276 + result = cc2.compare(cellA, cellB);
277 + result = dir2 == SortDir.ASC ? result : -result;
278 + }
279 + return result;
255 } 280 }
256 } 281 }
257 282
......
...@@ -20,13 +20,23 @@ import com.fasterxml.jackson.databind.node.ObjectNode; ...@@ -20,13 +20,23 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
20 import org.onosproject.ui.JsonUtils; 20 import org.onosproject.ui.JsonUtils;
21 import org.onosproject.ui.RequestHandler; 21 import org.onosproject.ui.RequestHandler;
22 22
23 +import static org.onosproject.ui.table.TableModel.sortDir;
24 +
23 /** 25 /**
24 * Message handler specifically for table views. 26 * Message handler specifically for table views.
25 */ 27 */
26 public abstract class TableRequestHandler extends RequestHandler { 28 public abstract class TableRequestHandler extends RequestHandler {
27 29
30 + private static final String FIRST_COL = "firstCol";
31 + private static final String FIRST_DIR = "firstDir";
32 + private static final String SECOND_COL = "secondCol";
33 + private static final String SECOND_DIR = "secondDir";
34 +
35 + private static final String ASC = "asc";
36 +
28 private static final String ANNOTS = "annots"; 37 private static final String ANNOTS = "annots";
29 private static final String NO_ROWS_MSG_KEY = "no_rows_msg"; 38 private static final String NO_ROWS_MSG_KEY = "no_rows_msg";
39 +
30 private final String respType; 40 private final String respType;
31 private final String nodeName; 41 private final String nodeName;
32 42
...@@ -51,9 +61,11 @@ public abstract class TableRequestHandler extends RequestHandler { ...@@ -51,9 +61,11 @@ public abstract class TableRequestHandler extends RequestHandler {
51 TableModel tm = createTableModel(); 61 TableModel tm = createTableModel();
52 populateTable(tm, payload); 62 populateTable(tm, payload);
53 63
54 - String sortCol = JsonUtils.string(payload, "sortCol", defaultColumnId()); 64 + String firstCol = JsonUtils.string(payload, FIRST_COL, defaultColumnId());
55 - String sortDir = JsonUtils.string(payload, "sortDir", "asc"); 65 + String firstDir = JsonUtils.string(payload, FIRST_DIR, ASC);
56 - tm.sort(sortCol, TableModel.sortDir(sortDir)); 66 + String secondCol = JsonUtils.string(payload, SECOND_COL, null);
67 + String secondDir = JsonUtils.string(payload, SECOND_DIR, null);
68 + tm.sort(firstCol, sortDir(firstDir), secondCol, sortDir(secondDir));
57 69
58 addTableConfigAnnotations(tm, payload); 70 addTableConfigAnnotations(tm, payload);
59 71
......
...@@ -35,6 +35,9 @@ public class TableModelTest { ...@@ -35,6 +35,9 @@ public class TableModelTest {
35 private static final String FOO = "foo"; 35 private static final String FOO = "foo";
36 private static final String BAR = "bar"; 36 private static final String BAR = "bar";
37 private static final String ZOO = "zoo"; 37 private static final String ZOO = "zoo";
38 + private static final String ID = "id";
39 + private static final String ALPHA = "alpha";
40 + private static final String NUMBER = "number";
38 41
39 private enum StarWars { 42 private enum StarWars {
40 LUKE_SKYWALKER, LEIA_ORGANA, HAN_SOLO, C3PO, R2D2, JABBA_THE_HUTT 43 LUKE_SKYWALKER, LEIA_ORGANA, HAN_SOLO, C3PO, R2D2, JABBA_THE_HUTT
...@@ -191,7 +194,7 @@ public class TableModelTest { ...@@ -191,7 +194,7 @@ public class TableModelTest {
191 initUnsortedTable(); 194 initUnsortedTable();
192 195
193 // sort by name 196 // sort by name
194 - tm.sort(FOO, SortDir.ASC); 197 + tm.sort(FOO, SortDir.ASC, null, null);
195 198
196 // verify results 199 // verify results
197 rows = tm.getRows(); 200 rows = tm.getRows();
...@@ -202,7 +205,7 @@ public class TableModelTest { ...@@ -202,7 +205,7 @@ public class TableModelTest {
202 } 205 }
203 206
204 // now the other way 207 // now the other way
205 - tm.sort(FOO, SortDir.DESC); 208 + tm.sort(FOO, SortDir.DESC, null, null);
206 209
207 // verify results 210 // verify results
208 rows = tm.getRows(); 211 rows = tm.getRows();
...@@ -219,7 +222,7 @@ public class TableModelTest { ...@@ -219,7 +222,7 @@ public class TableModelTest {
219 initUnsortedTable(); 222 initUnsortedTable();
220 223
221 // sort by number 224 // sort by number
222 - tm.sort(BAR, SortDir.ASC); 225 + tm.sort(BAR, SortDir.ASC, null, null);
223 226
224 // verify results 227 // verify results
225 rows = tm.getRows(); 228 rows = tm.getRows();
...@@ -230,7 +233,7 @@ public class TableModelTest { ...@@ -230,7 +233,7 @@ public class TableModelTest {
230 } 233 }
231 234
232 // now the other way 235 // now the other way
233 - tm.sort(BAR, SortDir.DESC); 236 + tm.sort(BAR, SortDir.DESC, null, null);
234 237
235 // verify results 238 // verify results
236 rows = tm.getRows(); 239 rows = tm.getRows();
...@@ -250,7 +253,7 @@ public class TableModelTest { ...@@ -250,7 +253,7 @@ public class TableModelTest {
250 tm.setFormatter(BAR, HexFormatter.INSTANCE); 253 tm.setFormatter(BAR, HexFormatter.INSTANCE);
251 254
252 // sort by number 255 // sort by number
253 - tm.sort(BAR, SortDir.ASC); 256 + tm.sort(BAR, SortDir.ASC, null, null);
254 257
255 // verify results 258 // verify results
256 rows = tm.getRows(); 259 rows = tm.getRows();
...@@ -276,7 +279,7 @@ public class TableModelTest { ...@@ -276,7 +279,7 @@ public class TableModelTest {
276 public void sortAndFormatTwo() { 279 public void sortAndFormatTwo() {
277 initUnsortedTable(); 280 initUnsortedTable();
278 tm.setFormatter(BAR, HexFormatter.INSTANCE); 281 tm.setFormatter(BAR, HexFormatter.INSTANCE);
279 - tm.sort(FOO, SortDir.ASC); 282 + tm.sort(FOO, SortDir.ASC, null, null);
280 rows = tm.getRows(); 283 rows = tm.getRows();
281 int nr = rows.length; 284 int nr = rows.length;
282 for (int i = 0; i < nr; i++) { 285 for (int i = 0; i < nr; i++) {
...@@ -324,7 +327,7 @@ public class TableModelTest { ...@@ -324,7 +327,7 @@ public class TableModelTest {
324 tm.addRow().cell(FOO, StarWars.R2D2); 327 tm.addRow().cell(FOO, StarWars.R2D2);
325 tm.addRow().cell(FOO, StarWars.LUKE_SKYWALKER); 328 tm.addRow().cell(FOO, StarWars.LUKE_SKYWALKER);
326 329
327 - tm.sort(FOO, SortDir.ASC); 330 + tm.sort(FOO, SortDir.ASC, null, null);
328 331
329 // verify expected results 332 // verify expected results
330 StarWars[] ordered = StarWars.values(); 333 StarWars[] ordered = StarWars.values();
...@@ -336,6 +339,102 @@ public class TableModelTest { ...@@ -336,6 +339,102 @@ public class TableModelTest {
336 } 339 }
337 } 340 }
338 341
342 +
343 + // ------------------------
344 + // Second sort column tests
345 +
346 + private static final String A1 = "a1";
347 + private static final String A2 = "a2";
348 + private static final String A3 = "a3";
349 + private static final String B1 = "b1";
350 + private static final String B2 = "b2";
351 + private static final String B3 = "b3";
352 + private static final String C1 = "c1";
353 + private static final String C2 = "c2";
354 + private static final String C3 = "c3";
355 + private static final String A = "A";
356 + private static final String B = "B";
357 + private static final String C = "C";
358 +
359 + private static final String[] UNSORTED_IDS = {
360 + A3, B2, A1, C2, A2, C3, B1, C1, B3
361 + };
362 + private static final String[] UNSORTED_ALPHAS = {
363 + A, B, A, C, A, C, B, C, B
364 + };
365 + private static final int[] UNSORTED_NUMBERS = {
366 + 3, 2, 1, 2, 2, 3, 1, 1, 3
367 + };
368 +
369 + private static final String[] ROW_ORDER_AA_NA = {
370 + A1, A2, A3, B1, B2, B3, C1, C2, C3
371 + };
372 + private static final String[] ROW_ORDER_AD_NA = {
373 + C1, C2, C3, B1, B2, B3, A1, A2, A3
374 + };
375 + private static final String[] ROW_ORDER_AA_ND = {
376 + A3, A2, A1, B3, B2, B1, C3, C2, C1
377 + };
378 + private static final String[] ROW_ORDER_AD_ND = {
379 + C3, C2, C1, B3, B2, B1, A3, A2, A1
380 + };
381 +
382 + private void testAddRow(TableModel tm, int index) {
383 + tm.addRow().cell(ID, UNSORTED_IDS[index])
384 + .cell(ALPHA, UNSORTED_ALPHAS[index])
385 + .cell(NUMBER, UNSORTED_NUMBERS[index]);
386 + }
387 +
388 + private TableModel unsortedDoubleTableModel() {
389 + tm = new TableModel(ID, ALPHA, NUMBER);
390 + for (int i = 0; i < 9; i++) {
391 + testAddRow(tm, i);
392 + }
393 + return tm;
394 + }
395 +
396 + private void verifyRowOrder(String tag, TableModel tm, String[] rowOrder) {
397 + int i = 0;
398 + for (TableModel.Row row : tm.getRows()) {
399 + assertEquals(tag + ": unexpected row id", rowOrder[i++], row.get(ID));
400 + }
401 + }
402 +
403 + @Test
404 + public void sortAlphaAscNumberAsc() {
405 + tm = unsortedDoubleTableModel();
406 + verifyRowOrder("unsorted", tm, UNSORTED_IDS);
407 + tm.sort(ALPHA, SortDir.ASC, NUMBER, SortDir.ASC);
408 + verifyRowOrder("aana", tm, ROW_ORDER_AA_NA);
409 + }
410 +
411 + @Test
412 + public void sortAlphaDescNumberAsc() {
413 + tm = unsortedDoubleTableModel();
414 + verifyRowOrder("unsorted", tm, UNSORTED_IDS);
415 + tm.sort(ALPHA, SortDir.DESC, NUMBER, SortDir.ASC);
416 + verifyRowOrder("adna", tm, ROW_ORDER_AD_NA);
417 + }
418 +
419 + @Test
420 + public void sortAlphaAscNumberDesc() {
421 + tm = unsortedDoubleTableModel();
422 + verifyRowOrder("unsorted", tm, UNSORTED_IDS);
423 + tm.sort(ALPHA, SortDir.ASC, NUMBER, SortDir.DESC);
424 + verifyRowOrder("aand", tm, ROW_ORDER_AA_ND);
425 + }
426 +
427 + @Test
428 + public void sortAlphaDescNumberDesc() {
429 + tm = unsortedDoubleTableModel();
430 + verifyRowOrder("unsorted", tm, UNSORTED_IDS);
431 + tm.sort(ALPHA, SortDir.DESC, NUMBER, SortDir.DESC);
432 + verifyRowOrder("adnd", tm, ROW_ORDER_AD_ND);
433 + }
434 +
435 + // ----------------
436 + // Annotation tests
437 +
339 @Test 438 @Test
340 public void stringAnnotation() { 439 public void stringAnnotation() {
341 tm = new TableModel(FOO); 440 tm = new TableModel(FOO);
......
...@@ -209,26 +209,16 @@ ...@@ -209,26 +209,16 @@
209 } 209 }
210 210
211 function sortIcons() { 211 function sortIcons() {
212 - function sortAsc(div) { 212 + function _s(div, gid) {
213 div.style('display', 'inline-block'); 213 div.style('display', 'inline-block');
214 - loadEmbeddedIcon(div, 'upArrow', 10); 214 + loadEmbeddedIcon(div, gid, 10);
215 div.classed('tableColSort', true); 215 div.classed('tableColSort', true);
216 } 216 }
217 217
218 - function sortDesc(div) {
219 - div.style('display', 'inline-block');
220 - loadEmbeddedIcon(div, 'downArrow', 10);
221 - div.classed('tableColSort', true);
222 - }
223 -
224 - function sortNone(div) {
225 - div.remove();
226 - }
227 -
228 return { 218 return {
229 - sortAsc: sortAsc, 219 + asc: function (div) { _s(div, 'upArrow'); },
230 - sortDesc: sortDesc, 220 + desc: function (div) { _s(div, 'downArrow'); },
231 - sortNone: sortNone 221 + none: function (div) { div.remove(); }
232 }; 222 };
233 } 223 }
234 224
......
...@@ -28,16 +28,11 @@ ...@@ -28,16 +28,11 @@
28 pdg = 22, 28 pdg = 22,
29 flashTime = 1500, 29 flashTime = 1500,
30 colWidth = 'col-width', 30 colWidth = 'col-width',
31 - tableIcon = 'table-icon', 31 + tableIcon = 'table-icon';
32 - asc = 'asc',
33 - desc = 'desc',
34 - none = 'none';
35 32
36 // internal state 33 // internal state
37 - var currCol = {}, 34 + var cstmWidths = {},
38 - prevCol = {}, 35 + api;
39 - cstmWidths = {},
40 - sortIconAPI;
41 36
42 // Functions for resizing a tabular view to the window 37 // Functions for resizing a tabular view to the window
43 38
...@@ -94,179 +89,208 @@ ...@@ -94,179 +89,208 @@
94 } 89 }
95 } 90 }
96 91
92 + // sort columns state model and functions
93 + var sortState = {
94 + s: {
95 + first: null,
96 + second: null,
97 + touched: null
98 + },
99 +
100 + reset: function () {
101 + var s = sortState.s;
102 + s.first && api.none(s.first.adiv);
103 + s.second && api.none(s.second.adiv);
104 + sortState.s = { first: null, second: null, touched: null };
105 + },
106 +
107 + touch: function (id, adiv) {
108 + var s = sortState.s,
109 + s1 = s.first,
110 + d;
111 +
112 + if (!s.touched) {
113 + s.first = { id: id, dir: 'asc', adiv: adiv };
114 + s.touched = id;
115 + } else {
116 + if (id === s.touched) {
117 + d = s1.dir === 'asc' ? 'desc' : 'asc';
118 + s1.dir = d;
119 + s1.adiv = adiv;
120 +
121 + } else {
122 + s.second = s.first;
123 + s.first = { id: id, dir: 'asc', adiv: adiv };
124 + s.touched = id;
125 + }
126 + }
127 + },
128 +
129 + update: function () {
130 + var s = sortState.s,
131 + s1 = s.first,
132 + s2 = s.second;
133 + api[s1.dir](s1.adiv);
134 + s2 && api.none(s2.adiv);
135 + }
136 + };
137 +
97 // Functions for sorting table rows by header 138 // Functions for sorting table rows by header
98 139
99 function updateSortDirection(thElem) { 140 function updateSortDirection(thElem) {
100 - sortIconAPI.sortNone(thElem.select('div')); 141 + var adiv = thElem.select('div'),
101 - currCol.div = thElem.append('div'); 142 + id = thElem.attr('colId');
102 - currCol.colId = thElem.attr('colId');
103 -
104 - if (currCol.colId === prevCol.colId) {
105 - (currCol.dir === desc) ? currCol.dir = asc : currCol.dir = desc;
106 - prevCol.dir = currCol.dir;
107 - } else {
108 - currCol.dir = asc;
109 - prevCol.dir = none;
110 - }
111 - (currCol.dir === asc) ?
112 - sortIconAPI.sortAsc(currCol.div) : sortIconAPI.sortDesc(currCol.div);
113 -
114 - if (prevCol.colId && prevCol.dir === none) {
115 - sortIconAPI.sortNone(prevCol.div);
116 - }
117 143
118 - prevCol.colId = currCol.colId; 144 + api.none(adiv);
119 - prevCol.div = currCol.div; 145 + adiv = thElem.append('div');
146 + sortState.touch(id, adiv);
147 + sortState.update();
120 } 148 }
121 149
122 function sortRequestParams() { 150 function sortRequestParams() {
151 + var s = sortState.s,
152 + s1 = s.first,
153 + s2 = s.second,
154 + id2 = s2 && s2.id,
155 + dir2 = s2 && s2.dir;
123 return { 156 return {
124 - sortCol: currCol.colId, 157 + firstCol: s1.id,
125 - sortDir: currCol.dir 158 + firstDir: s1.dir,
159 + secondCol: id2,
160 + secondDir: dir2
126 }; 161 };
127 } 162 }
128 163
129 - function resetSort() {
130 - if (currCol.div) {
131 - sortIconAPI.sortNone(currCol.div);
132 - }
133 - if (prevCol.div) {
134 - sortIconAPI.sortNone(prevCol.div);
135 - }
136 - currCol = {};
137 - prevCol = {};
138 - }
139 -
140 angular.module('onosWidget') 164 angular.module('onosWidget')
141 - .directive('onosTableResize', ['$log','$window', 165 + .directive('onosTableResize', ['$log','$window', 'FnService', 'MastService',
142 - 'FnService', 'MastService', 166 +
143 - 167 + function (_$log_, _$window_, _fs_, _mast_) {
144 - function (_$log_, _$window_, _fs_, _mast_) { 168 + return function (scope, element) {
145 - return function (scope, element) { 169 + $log = _$log_;
146 - $log = _$log_; 170 + $window = _$window_;
147 - $window = _$window_; 171 + fs = _fs_;
148 - fs = _fs_; 172 + mast = _mast_;
149 - mast = _mast_; 173 +
150 - 174 + var table = d3.select(element[0]),
151 - var table = d3.select(element[0]), 175 + tableElems = {
152 - tableElems = { 176 + table: table,
153 - table: table, 177 + thead: table.select('.table-header').select('table'),
154 - thead: table.select('.table-header').select('table'), 178 + tbody: table.select('.table-body').select('table')
155 - tbody: table.select('.table-body').select('table') 179 + },
156 - }, 180 + wsz;
157 - wsz; 181 +
158 - 182 + findCstmWidths(table);
159 - findCstmWidths(table); 183 +
160 - 184 + // adjust table on window resize
161 - // adjust table on window resize 185 + scope.$watchCollection(function () {
162 - scope.$watchCollection(function () { 186 + return {
163 - return { 187 + h: $window.innerHeight,
164 - h: $window.innerHeight, 188 + w: $window.innerWidth
165 - w: $window.innerWidth 189 + };
166 - }; 190 + }, function () {
167 - }, function () { 191 + wsz = fs.windowSize(0, 30);
168 - wsz = fs.windowSize(0, 30); 192 + adjustTable(
169 - adjustTable( 193 + scope.tableData.length,
170 - scope.tableData.length, 194 + tableElems,
171 - tableElems, 195 + wsz.width, wsz.height
172 - wsz.width, wsz.height 196 + );
173 - ); 197 + });
174 - }); 198 +
199 + // adjust table when data changes
200 + scope.$watchCollection('tableData', function () {
201 + adjustTable(
202 + scope.tableData.length,
203 + tableElems,
204 + wsz.width, wsz.height
205 + );
206 + });
207 +
208 + scope.$on('$destroy', function () {
209 + cstmWidths = {};
210 + });
211 + };
212 + }])
175 213
176 - // adjust table when data changes 214 + .directive('onosSortableHeader', ['$log', 'IconService',
177 - scope.$watchCollection('tableData', function () { 215 + function (_$log_, _is_) {
178 - adjustTable( 216 + return function (scope, element) {
179 - scope.tableData.length, 217 + $log = _$log_;
180 - tableElems, 218 + is = _is_;
181 - wsz.width, wsz.height 219 + var header = d3.select(element[0]);
182 - );
183 - });
184 220
185 - scope.$on('$destroy', function () { 221 + api = is.sortIcons();
186 - cstmWidths = {};
187 - });
188 - };
189 - }])
190 -
191 - .directive('onosSortableHeader', ['$log', 'IconService',
192 - function (_$log_, _is_) {
193 - return function (scope, element) {
194 - $log = _$log_;
195 - is = _is_;
196 - var header = d3.select(element[0]);
197 - sortIconAPI = is.sortIcons();
198 -
199 - header.selectAll('td').on('click', function () {
200 - var col = d3.select(this);
201 -
202 - if (col.attr('sortable') === '') {
203 - updateSortDirection(col);
204 - scope.sortParams = sortRequestParams();
205 - scope.sortCallback(scope.sortParams);
206 - }
207 - });
208 222
209 - scope.$on('$destroy', function () { 223 + header.selectAll('td').on('click', function () {
210 - resetSort(); 224 + var col = d3.select(this);
211 - }); 225 +
212 - }; 226 + if (col.attr('sortable') === '') {
213 - }]) 227 + updateSortDirection(col);
214 - 228 + scope.sortParams = sortRequestParams();
215 - .directive('onosFlashChanges', 229 + scope.sortCallback(scope.sortParams);
216 - ['$log', '$parse', '$timeout', 'FnService',
217 - function ($log, $parse, $timeout, fs) {
218 -
219 - return function (scope, element, attrs) {
220 - var idProp = attrs.idProp,
221 - table = d3.select(element[0]),
222 - trs, promise;
223 -
224 - function highlightRows() {
225 - var changedRows = [];
226 - function classRows(b) {
227 - if (changedRows.length) {
228 - angular.forEach(changedRows, function (tr) {
229 - tr.classed('data-change', b);
230 - });
231 - }
232 - }
233 - // timeout because 'row-id' was the un-interpolated value
234 - // "{{link.one}}" for example, instead of link.one evaluated
235 - // timeout executes on the next digest -- after evaluation
236 - $timeout(function () {
237 - if (scope.tableData.length) {
238 - trs = table.selectAll('tr');
239 - }
240 -
241 - if (trs && !trs.empty()) {
242 - trs.each(function () {
243 - var tr = d3.select(this);
244 - if (fs.find(tr.attr('row-id'),
245 - scope.changedData,
246 - idProp) > -1) {
247 - changedRows.push(tr);
248 - }
249 - });
250 - classRows(true);
251 - promise = $timeout(function () {
252 - classRows(false);
253 - }, flashTime);
254 - trs = undefined;
255 - }
256 - });
257 } 230 }
231 + });
258 232
259 - // new items added: 233 + scope.$on('$destroy', function () {
260 - scope.$on('ngRepeatComplete', highlightRows); 234 + sortState.reset();
261 - // items changed in existing set: 235 + });
262 - scope.$watchCollection('changedData', highlightRows); 236 + };
237 + }])
238 +
239 + .directive('onosFlashChanges',
240 + ['$log', '$parse', '$timeout', 'FnService',
241 + function ($log, $parse, $timeout, fs) {
242 +
243 + return function (scope, element, attrs) {
244 + var idProp = attrs.idProp,
245 + table = d3.select(element[0]),
246 + trs, promise;
247 +
248 + function highlightRows() {
249 + var changedRows = [];
250 + function classRows(b) {
251 + if (changedRows.length) {
252 + angular.forEach(changedRows, function (tr) {
253 + tr.classed('data-change', b);
254 + });
255 + }
256 + }
257 + // timeout because 'row-id' was the un-interpolated value
258 + // "{{link.one}}" for example, instead of link.one evaluated
259 + // timeout executes on the next digest -- after evaluation
260 + $timeout(function () {
261 + if (scope.tableData.length) {
262 + trs = table.selectAll('tr');
263 + }
263 264
264 - scope.$on('$destroy', function () { 265 + if (trs && !trs.empty()) {
265 - if (promise) { 266 + trs.each(function () {
266 - $timeout.cancel(promise); 267 + var tr = d3.select(this);
268 + if (fs.find(tr.attr('row-id'),
269 + scope.changedData,
270 + idProp) > -1) {
271 + changedRows.push(tr);
272 + }
273 + });
274 + classRows(true);
275 + promise = $timeout(function () {
276 + classRows(false);
277 + }, flashTime);
278 + trs = undefined;
267 } 279 }
268 }); 280 });
269 - }; 281 + }
270 - }]); 282 +
283 + // new items added:
284 + scope.$on('ngRepeatComplete', highlightRows);
285 + // items changed in existing set:
286 + scope.$watchCollection('changedData', highlightRows);
287 +
288 + scope.$on('$destroy', function () {
289 + if (promise) {
290 + $timeout.cancel(promise);
291 + }
292 + });
293 + };
294 + }]);
271 295
272 }()); 296 }());
......
...@@ -77,8 +77,10 @@ ...@@ -77,8 +77,10 @@
77 respCb: refreshCtrls, 77 respCb: refreshCtrls,
78 // pre-populate sort so active apps are at the top of the list 78 // pre-populate sort so active apps are at the top of the list
79 sortParams: { 79 sortParams: {
80 - sortCol: 'state', 80 + firstCol: 'state',
81 - sortDir: 'desc' 81 + firstDir: 'desc',
82 + secondCol: 'id',
83 + secondDir: 'asc'
82 } 84 }
83 }); 85 });
84 86
......
...@@ -39,7 +39,7 @@ ...@@ -39,7 +39,7 @@
39 <table> 39 <table>
40 <tr> 40 <tr>
41 <td colId="available" class="table-icon" sortable></td> 41 <td colId="available" class="table-icon" sortable></td>
42 - <td colId="type" class="table-icon" sortable></td> 42 + <td colId="type" class="table-icon"></td>
43 <td colId="name" sortable>Friendly Name </td> 43 <td colId="name" sortable>Friendly Name </td>
44 <td colId="id" sortable>Device ID </td> 44 <td colId="id" sortable>Device ID </td>
45 <td colId="masterid" sortable>Master Instance </td> 45 <td colId="masterid" sortable>Master Instance </td>
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
14 <div class="table-header" onos-sortable-header> 14 <div class="table-header" onos-sortable-header>
15 <table> 15 <table>
16 <tr> 16 <tr>
17 - <td colId="type" class="table-icon" sortable></td> 17 + <td colId="type" class="table-icon"></td>
18 <td colId="id" sortable>Host ID </td> 18 <td colId="id" sortable>Host ID </td>
19 <td colId="mac" sortable>MAC Address </td> 19 <td colId="mac" sortable>MAC Address </td>
20 <td colId="vlan" sortable>VLAN ID </td> 20 <td colId="vlan" sortable>VLAN ID </td>
......