From 5e41de53700f6f0a9bebb8c242f55c820313b167 Mon Sep 17 00:00:00 2001 From: alecmerdler Date: Sat, 27 May 2017 01:15:28 -0700 Subject: [PATCH] maintain fixed-width columns when filtering cor-table --- static/css/core-ui.css | 7 +- .../app-public-view.component.html | 7 +- .../ui/app-public-view/channels-list.html | 22 ++- .../ui/cor-table/cor-table-col.component.ts | 2 + .../ui/cor-table/cor-table.component.css | 5 + .../ui/cor-table/cor-table.component.html | 28 +++- .../ui/cor-table/cor-table.component.spec.ts | 126 ++++++++++++++++++ .../ui/cor-table/cor-table.component.ts | 55 ++++++-- .../manage-trigger.component.html | 16 ++- 9 files changed, 234 insertions(+), 34 deletions(-) create mode 100644 static/js/directives/ui/cor-table/cor-table.component.css create mode 100644 static/js/directives/ui/cor-table/cor-table.component.spec.ts diff --git a/static/css/core-ui.css b/static/css/core-ui.css index d894b95b4..d28c74129 100644 --- a/static/css/core-ui.css +++ b/static/css/core-ui.css @@ -973,13 +973,18 @@ a:focus { table-layout: fixed; } -.co-fixed-table .co-flowing-col{ +.co-fixed-table .co-flowing-col { overflow: hidden; text-overflow: ellipsis; padding-left: 16px; vertical-align: middle; } +.co-fixed-table .nowrap-col { + white-space: nowrap; + overflow: hidden; +} + .co-table td { border-bottom: 1px solid #eee; padding: 10px; diff --git a/static/js/directives/ui/app-public-view/app-public-view.component.html b/static/js/directives/ui/app-public-view/app-public-view.component.html index e47f7ae74..1430247a1 100644 --- a/static/js/directives/ui/app-public-view/app-public-view.component.html +++ b/static/js/directives/ui/app-public-view/app-public-view.component.html @@ -75,13 +75,16 @@
- + -
diff --git a/static/js/directives/ui/app-public-view/channels-list.html b/static/js/directives/ui/app-public-view/channels-list.html index 2c5bd38dc..8fa843a2f 100644 --- a/static/js/directives/ui/app-public-view/channels-list.html +++ b/static/js/directives/ui/app-public-view/channels-list.html @@ -1,4 +1,18 @@ - - - -(None) \ No newline at end of file +
+
+ + + + +
+ + + {{ $ctrl.rows[rowIndex].expanded ? 'show less...' : item.channels.length - col.itemLimit + ' more...' }} + + (None) +
diff --git a/static/js/directives/ui/cor-table/cor-table-col.component.ts b/static/js/directives/ui/cor-table/cor-table-col.component.ts index 9a53141bd..ec4523257 100644 --- a/static/js/directives/ui/cor-table/cor-table-col.component.ts +++ b/static/js/directives/ui/cor-table/cor-table-col.component.ts @@ -10,6 +10,7 @@ import { CorTableComponent } from './cor-table.component'; template: '', }) export class CorTableColumn implements OnInit { + @Input('@') public title: string; @Input('@') public templateurl: string; @Input('@') public datafield: string; @@ -19,6 +20,7 @@ export class CorTableColumn implements OnInit { @Input('@') public style: string; @Input('@') public class: string; @Input('@') public kindof: string; + @Input('<') public itemLimit: number = 5; constructor(@Host() @Inject(CorTableComponent) private parent: CorTableComponent, @Inject('TableService') private tableService: any) { diff --git a/static/js/directives/ui/cor-table/cor-table.component.css b/static/js/directives/ui/cor-table/cor-table.component.css new file mode 100644 index 000000000..cd128e1fd --- /dev/null +++ b/static/js/directives/ui/cor-table/cor-table.component.css @@ -0,0 +1,5 @@ +.cor-table-element .co-top-bar { + display: flex; + justify-content: flex-end; + align-items: baseline; +} diff --git a/static/js/directives/ui/cor-table/cor-table.component.html b/static/js/directives/ui/cor-table/cor-table.component.html index f6d18048c..6e17d0574 100644 --- a/static/js/directives/ui/cor-table/cor-table.component.html +++ b/static/js/directives/ui/cor-table/cor-table.component.html @@ -1,8 +1,8 @@
- + -
+
+ + +
+
+ + +
+
@@ -24,20 +38,20 @@
- +
- @@ -49,4 +63,4 @@
No matching {{ ::$ctrl.tableItemTitle }} found.
Try adjusting your filter above.
- \ No newline at end of file + diff --git a/static/js/directives/ui/cor-table/cor-table.component.spec.ts b/static/js/directives/ui/cor-table/cor-table.component.spec.ts new file mode 100644 index 000000000..eb7a1c597 --- /dev/null +++ b/static/js/directives/ui/cor-table/cor-table.component.spec.ts @@ -0,0 +1,126 @@ +import { Mock } from 'ts-mocks'; +import { CorTableComponent, CorTableOptions } from './cor-table.component'; +import { CorTableColumn } from './cor-table-col.component'; +import { SimpleChanges } from 'ng-metadata/core'; +import { ViewArray } from '../../../services/view-array/view-array'; +import Spy = jasmine.Spy; + + +describe("CorTableComponent", () => { + var component: CorTableComponent; + var tableServiceMock: Mock; + var tableData: any[]; + var columnMocks: Mock[]; + var orderedDataMock: Mock; + + beforeEach(() => { + orderedDataMock = new Mock(); + orderedDataMock.setup(mock => mock.visibleEntries).is([]); + tableServiceMock = new Mock(); + tableServiceMock.setup(mock => mock.buildOrderedItems) + .is((items, options, filterFields, numericFields, extrafilter?) => orderedDataMock.Object); + + tableData = [ + {name: "apple", last_modified: 1496068383000, version: "1.0.0"}, + {name: "pear", last_modified: 1496068383001, version: "1.1.0"}, + {name: "orange", last_modified: 1496068383002, version: "1.0.0"}, + {name: "banana", last_modified: 1496068383000, version: "2.0.0"}, + ]; + + columnMocks = Object.keys(tableData[0]) + .map((key, index) => { + const col = new Mock(); + col.setup(mock => mock.isNumeric).is(() => index == 1 ? true : false); + col.setup(mock => mock.processColumnForOrdered).is((value) => "dummy"); + col.setup(mock => mock.datafield).is(key); + + return col; + }); + + component = new CorTableComponent(tableServiceMock.Object); + component.tableData = tableData; + component.filterFields = ['name', 'version']; + component.compact = false; + component.tableItemTitle = "fruits"; + component.maxDisplayCount = 10; + // Add columns + columnMocks.forEach(colMock => component.addColumn(colMock.Object)); + (tableServiceMock.Object.buildOrderedItems).calls.reset(); + }); + + describe("constructor", () => { + + it("sets table options", () => { + expect(component.options.filter).toEqual(''); + expect(component.options.reverse).toBe(false); + expect(component.options.predicate).toEqual(''); + expect(component.options.page).toEqual(0); + }); + }); + + describe("ngOnChanges", () => { + var changes: SimpleChanges; + + it("calls table service to build ordered items if table data is changed", () => { + changes = {tableData: {currentValue: [], previousValue: [], isFirstChange: () => false}}; + component.ngOnChanges(changes); + + expect((tableServiceMock.Object.buildOrderedItems)).toHaveBeenCalled(); + }); + + it("passes processed table data to table service", () => { + changes = {tableData: {currentValue: [], previousValue: [], isFirstChange: () => false}}; + component.tableData = changes['tableData'].currentValue; + component.ngOnChanges(changes); + + expect((tableServiceMock.Object.buildOrderedItems).calls.argsFor(0)[0]).not.toEqual(tableData); + }); + + it("passes options to table service", () => { + changes = {tableData: {currentValue: [], previousValue: [], isFirstChange: () => false}}; + component.ngOnChanges(changes); + + expect((tableServiceMock.Object.buildOrderedItems).calls.argsFor(0)[1]).toEqual(component.options); + }); + + it("passes filter fields to table service", () => { + changes = {tableData: {currentValue: [], previousValue: [], isFirstChange: () => false}}; + component.ngOnChanges(changes); + + expect((tableServiceMock.Object.buildOrderedItems).calls.argsFor(0)[2]).toEqual(component.filterFields); + }); + + it("passes numeric fields to table service", () => { + changes = {tableData: {currentValue: [], previousValue: [], isFirstChange: () => false}}; + component.ngOnChanges(changes); + + const expectedNumericCols: string[] = columnMocks.filter(colMock => colMock.Object.isNumeric()) + .map(colMock => colMock.Object.datafield); + + expect((tableServiceMock.Object.buildOrderedItems).calls.argsFor(0)[3]).toEqual(expectedNumericCols); + }); + + it("resets to first page if table data is changed", () => { + component.options.page = 1; + changes = {tableData: {currentValue: [], previousValue: [], isFirstChange: () => false}}; + component.ngOnChanges(changes); + + expect(component.options.page).toEqual(0); + }); + }); + + describe("addColumn", () => { + var columnMock: Mock; + + beforeEach(() => { + columnMock = new Mock(); + columnMock.setup(mock => mock.isNumeric).is(() => false); + }); + + it("calls table service to build ordered items with new column", () => { + component.addColumn(columnMock.Object); + + expect((tableServiceMock.Object.buildOrderedItems)).toHaveBeenCalled(); + }); + }); +}); diff --git a/static/js/directives/ui/cor-table/cor-table.component.ts b/static/js/directives/ui/cor-table/cor-table.component.ts index 30c08baf8..d858d11b2 100644 --- a/static/js/directives/ui/cor-table/cor-table.component.ts +++ b/static/js/directives/ui/cor-table/cor-table.component.ts @@ -1,5 +1,7 @@ import { Input, Component, OnChanges, SimpleChanges, Inject } from 'ng-metadata/core'; import { CorTableColumn } from './cor-table-col.component'; +import { ViewArray } from '../../../services/view-array/view-array'; +import './cor-table.component.css'; /** @@ -13,23 +15,28 @@ import { CorTableColumn } from './cor-table-col.component'; } }) export class CorTableComponent implements OnChanges { + @Input('<') public tableData: any[] = []; @Input('@') public tableItemTitle: string; @Input('<') public filterFields: string[]; - @Input('@') public compact: string; + @Input('<') public compact: boolean = false; @Input('<') public maxDisplayCount: number = 10; - private columns: CorTableColumn[]; - private orderedData: any; - private options: any; + @Input('<') public canExpand: boolean = false; + @Input('<') public expandRows: boolean = false; + + public orderedData: ViewArray; + public options: CorTableOptions = { + filter: '', + reverse: false, + predicate: '', + page: 0, + }; + + private rows: CorTableRow[] = []; + private columns: CorTableColumn[] = []; constructor(@Inject('TableService') private tableService: any) { - this.columns = []; - this.options = { - 'filter': '', - 'reverse': false, - 'predicate': '', - 'page': 0, - }; + } public ngOnChanges(changes: SimpleChanges): void { @@ -53,6 +60,11 @@ export class CorTableComponent implements OnChanges { this.refreshOrder(); } + private setExpanded(isExpanded: boolean): void { + this.expandRows = isExpanded; + this.rows.forEach((row) => row.expanded = isExpanded); + } + private tablePredicateClass(col: CorTableColumn, options: any) { return this.tableService.tablePredicateClass(col.datafield, this.options.predicate, this.options.reverse); } @@ -60,15 +72,15 @@ export class CorTableComponent implements OnChanges { private refreshOrder(): void { this.options.page = 0; - var columnMap = {}; + var columnMap: {[name: string]: CorTableColumn} = {}; this.columns.forEach(function(col) { columnMap[col.datafield] = col; }); - const numericCols = this.columns.filter(col => col.isNumeric()) + const numericCols: string[] = this.columns.filter(col => col.isNumeric()) .map(col => col.datafield); - const processed = this.tableData.map((item) => { + const processed: any[] = this.tableData.map((item) => { Object.keys(item).forEach((key) => { if (columnMap[key]) { item[key] = columnMap[key].processColumnForOrdered(item[key]); @@ -79,5 +91,20 @@ export class CorTableComponent implements OnChanges { }); this.orderedData = this.tableService.buildOrderedItems(processed, this.options, this.filterFields, numericCols); + this.rows = this.orderedData.visibleEntries.map((item) => Object.assign({}, {expanded: false, rowData: item})); } } + + +export type CorTableOptions = { + filter: string; + reverse: boolean; + predicate: string; + page: number; +}; + + +export type CorTableRow = { + expanded: boolean; + rowData: any; +}; diff --git a/static/js/directives/ui/manage-trigger/manage-trigger.component.html b/static/js/directives/ui/manage-trigger/manage-trigger.component.html index aa0979b6b..517dc15fd 100644 --- a/static/js/directives/ui/manage-trigger/manage-trigger.component.html +++ b/static/js/directives/ui/manage-trigger/manage-trigger.component.html @@ -17,7 +17,7 @@ max-display-count="$ctrl.namespacesPerPage" filter-fields="::['title', 'id']"> @@ -100,7 +100,8 @@ max-display-count="$ctrl.repositoriesPerPage" filter-fields="['name', 'description']"> + templateurl="/static/js/directives/ui/manage-trigger-githost/repository-radio-input.html" + style="width: 30px;"> @@ -376,7 +380,7 @@ max-display-count="$ctrl.robotsPerPage">
{{ ::col.title }}
-
+ style="{{ ::col.style }}" class="{{ ::col.class }}"> +
{{ item[col.datafield] }}