initial import for Open Source 🎉

This commit is contained in:
Jimmy Zelinskie 2019-11-12 11:09:47 -05:00
parent 1898c361f3
commit 9c0dd3b722
2048 changed files with 218743 additions and 0 deletions

View file

@ -0,0 +1,45 @@
import { Input, Component, OnInit, Inject, Host } from 'ng-metadata/core';
import { CorTableComponent } from './cor-table.component';
/**
* Defines a column (optionally sortable) in the table.
*/
@Component({
selector: 'cor-table-col',
template: '',
})
export class CorTableColumn implements OnInit {
@Input('@') public title: string;
@Input('@') public templateurl: string;
@Input('@') public datafield: string;
@Input('@') public sortfield: string;
@Input('@') public selected: string;
@Input('=') public bindModel: any;
@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) {
}
public ngOnInit(): void {
this.parent.addColumn(this);
}
public isNumeric(): boolean {
return this.kindof == 'datetime';
}
public processColumnForOrdered(value: any): any {
if (this.kindof == 'datetime' && value) {
return this.tableService.getReversedTimestamp(value);
}
return value;
}
}

View file

@ -0,0 +1,5 @@
.cor-table-element .co-top-bar {
display: flex;
justify-content: flex-end;
align-items: baseline;
}

View file

@ -0,0 +1,67 @@
<div class="cor-table-element">
<span ng-transclude></span>
<!-- Filter -->
<div class="co-top-bar" ng-if="!$ctrl.compact">
<span class="co-filter-box with-options" ng-if="$ctrl.tableData.length && $ctrl.filterFields.length">
<span class="page-controls"
total-count="$ctrl.orderedData.entries.length"
current-page="$ctrl.options.page"
page-size="$ctrl.maxDisplayCount"></span>
<span class="filter-message" ng-if="$ctrl.options.filter">
Showing {{ $ctrl.orderedData.entries.length }} of {{ $ctrl.tableData.length }} {{ ::$ctrl.tableItemTitle }}
</span>
<input class="form-control" type="text"
placeholder="Filter {{ ::$ctrl.tableItemTitle }}..."
ng-model="$ctrl.options.filter"
ng-change="$ctrl.refreshOrder()">
</span>
<!-- Compact/expand rows toggle -->
<div ng-if="!$ctrl.compact && $ctrl.canExpand" class="tab-header-controls">
<div class="btn-group btn-group-sm">
<button class="btn" ng-class="!$ctrl.expandRows ? 'btn-primary active' : 'btn-default'"
ng-click="$ctrl.setExpanded(false)">
Compact
</button>
<button class="btn" ng-class="$ctrl.expandRows ? 'btn-info active' : 'btn-default'"
ng-click="$ctrl.setExpanded(true)">
Expanded
</button>
</div>
</div>
</div>
<!-- Empty -->
<div class="empty" ng-if="!$ctrl.tableData.length && $ctrl.compact != 'true'">
<div class="empty-primary-msg">No {{ ::$ctrl.tableItemTitle }} found.</div>
</div>
<!-- Table -->
<table class="co-table co-fixed-table" ng-show="$ctrl.tableData.length">
<thead>
<td ng-repeat="col in $ctrl.columns"
ng-class="$ctrl.tablePredicateClass(col)" style="{{ ::col.style }}"
class="{{ ::col.class }}">
<a ng-click="$ctrl.setOrder(col)">{{ ::col.title }}</a>
</td>
</thead>
<tbody ng-repeat="item in $ctrl.orderedData.visibleEntries" ng-init="rowIndex = $index"
ng-if="($index >= $ctrl.options.page * $ctrl.maxDisplayCount &&
$index < ($ctrl.options.page + 1) * $ctrl.maxDisplayCount)">
<tr>
<td ng-repeat="col in $ctrl.columns"
style="{{ ::col.style }}" class="{{ ::col.class }}">
<div ng-if="col.templateurl" ng-include="col.templateurl"></div>
<div ng-if="!col.templateurl">{{ item[col.datafield] }}</div>
</td>
</tr>
</tbody>
</table>
<div class="empty" ng-if="!$ctrl.orderedData.entries.length && $ctrl.tableData.length"
style="margin-top: 20px;">
<div class="empty-primary-msg">No matching {{ ::$ctrl.tableItemTitle }} found.</div>
<div class="empty-secondary-msg">Try adjusting your filter above.</div>
</div>
</div>

View file

@ -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<any>;
var tableData: any[];
var columnMocks: Mock<CorTableColumn>[];
var orderedDataMock: Mock<ViewArray>;
beforeEach(() => {
orderedDataMock = new Mock<ViewArray>();
orderedDataMock.setup(mock => mock.visibleEntries).is([]);
tableServiceMock = new Mock<any>();
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<CorTableColumn>();
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));
(<Spy>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((<Spy>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((<Spy>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((<Spy>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((<Spy>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((<Spy>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<CorTableColumn>;
beforeEach(() => {
columnMock = new Mock<CorTableColumn>();
columnMock.setup(mock => mock.isNumeric).is(() => false);
});
it("calls table service to build ordered items with new column", () => {
component.addColumn(columnMock.Object);
expect((<Spy>tableServiceMock.Object.buildOrderedItems)).toHaveBeenCalled();
});
});
});

View file

@ -0,0 +1,110 @@
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';
/**
* A component that displays a table of information, with optional filtering and automatic sorting.
*/
@Component({
selector: 'cor-table',
templateUrl: '/static/js/directives/ui/cor-table/cor-table.component.html',
legacy: {
transclude: true
}
})
export class CorTableComponent implements OnChanges {
@Input('<') public tableData: any[] = [];
@Input('@') public tableItemTitle: string;
@Input('<') public filterFields: string[];
@Input('<') public compact: boolean = false;
@Input('<') public maxDisplayCount: number = 10;
@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) {
}
public ngOnChanges(changes: SimpleChanges): void {
if (changes['tableData'] !== undefined) {
this.refreshOrder();
}
}
public addColumn(col: CorTableColumn): void {
this.columns.push(col);
if (col.selected == 'true') {
this.options['predicate'] = col.datafield;
}
this.refreshOrder();
}
private setOrder(col: CorTableColumn): void {
this.tableService.orderBy(col.datafield, this.options);
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);
}
private refreshOrder(): void {
this.options.page = 0;
var columnMap: {[name: string]: CorTableColumn} = {};
this.columns.forEach(function(col) {
columnMap[col.datafield] = col;
});
const numericCols: string[] = this.columns.filter(col => col.isNumeric())
.map(col => col.datafield);
const processed: any[] = this.tableData.map((item) => {
Object.keys(item).forEach((key) => {
if (columnMap[key]) {
item[key] = columnMap[key].processColumnForOrdered(item[key]);
}
});
return item;
});
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;
};