import { HttpClientTestingModule } from '@angular/common/http/testing';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { ComponentsModule } from '../../../shared/components/components.module';
+import { DataTableModule } from '../../../shared/datatable/datatable.module';
import { RgwDaemonService } from '../services/rgw-daemon.service';
import { RgwDaemonListComponent } from './rgw-daemon-list.component';
TestBed.configureTestingModule({
declarations: [ RgwDaemonListComponent ],
imports: [
- ComponentsModule,
+ DataTableModule,
HttpClientTestingModule,
HttpClientModule
],
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
+import { ChartsModule } from 'ng2-charts/ng2-charts';
import { AlertModule } from 'ngx-bootstrap';
-import { DataTableModule } from './datatable/datatable.module';
import { ViewCacheComponent } from './view-cache/view-cache.component';
@NgModule({
imports: [
CommonModule,
- DataTableModule,
- AlertModule.forRoot()
+ AlertModule.forRoot(),
+ ChartsModule
],
declarations: [ViewCacheComponent],
providers: [],
exports: [
- DataTableModule,
ViewCacheComponent
]
})
+++ /dev/null
-import { CommonModule } from '@angular/common';
-import { NgModule } from '@angular/core';
-import { FormsModule } from '@angular/forms';
-
-import { NgxDatatableModule } from '@swimlane/ngx-datatable';
-
-import { PipesModule } from '../../pipes/pipes.module';
-import { TableDetailsDirective } from './table-details.directive';
-import { TableKeyValueComponent } from './table-key-value/table-key-value.component';
-import { TableComponent } from './table/table.component';
-
-@NgModule({
- imports: [
- CommonModule,
- NgxDatatableModule,
- FormsModule,
- PipesModule
- ],
- declarations: [
- TableComponent,
- TableDetailsDirective,
- TableKeyValueComponent
- ],
- exports: [
- TableComponent,
- NgxDatatableModule,
- TableKeyValueComponent
- ]
-})
-export class DataTableModule { }
+++ /dev/null
-import { TableDetailsDirective } from './table-details.directive';
-
-describe('TableDetailsDirective', () => {
- it('should create an instance', () => {
- const directive = new TableDetailsDirective(null);
- expect(directive).toBeTruthy();
- });
-});
+++ /dev/null
-import { Directive, Input, ViewContainerRef } from '@angular/core';
-
-@Directive({
- selector: '[cdTableDetails]'
-})
-export class TableDetailsDirective {
- @Input() selected?: any[];
-
- constructor(public viewContainerRef: ViewContainerRef) { }
-
-}
+++ /dev/null
-<cd-table [data]="data"
- [columns]="columns"
- columnMode="flex"
- [toolHeader]="false"
- [header]="false"
- [footer]="false"
- [limit]="0">
-</cd-table>
+++ /dev/null
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule } from '@angular/forms';
-
-import { NgxDatatableModule } from '@swimlane/ngx-datatable';
-
-import { TableComponent } from '../table/table.component';
-import { TableKeyValueComponent } from './table-key-value.component';
-
-describe('TableKeyValueComponent', () => {
- let component: TableKeyValueComponent;
- let fixture: ComponentFixture<TableKeyValueComponent>;
-
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- declarations: [ TableComponent, TableKeyValueComponent ],
- imports: [ FormsModule, NgxDatatableModule ]
- })
- .compileComponents();
- }));
-
- beforeEach(() => {
- fixture = TestBed.createComponent(TableKeyValueComponent);
- component = fixture.componentInstance;
- fixture.detectChanges();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-});
+++ /dev/null
-import { Component, Input, OnInit } from '@angular/core';
-
-import { CellTemplate } from '../../../enum/cell-template.enum';
-import { CdTableColumn } from '../../../models/cd-table-column';
-
-/**
- * Display the given data in a 2 column data table. The left column
- * shows the 'key' attribute, the right column the 'value' attribute.
- * The data table has the following characteristics:
- * - No header and footer is displayed
- * - The relation of the width for the columns 'key' and 'value' is 1:3
- * - The 'key' column is displayed in bold text
- */
-@Component({
- selector: 'cd-table-key-value',
- templateUrl: './table-key-value.component.html',
- styleUrls: ['./table-key-value.component.scss']
-})
-export class TableKeyValueComponent implements OnInit {
-
- columns: Array<CdTableColumn> = [];
-
- /**
- * An array of objects to be displayed in the data table.
- */
- @Input() data: Array<object> = [];
-
- /**
- * The name of the attribute to be displayed as key.
- * Defaults to 'key'.
- * @type {string}
- */
- @Input() key = 'key';
-
- /**
- * The name of the attribute to be displayed as value.
- * Defaults to 'value'.
- * @type {string}
- */
- @Input() value = 'value';
-
- constructor() { }
-
- ngOnInit() {
- this.columns = [
- {
- prop: this.key,
- flexGrow: 1,
- cellTransformation: CellTemplate.bold
- },
- {
- prop: this.value,
- flexGrow: 3
- }
- ];
- }
-}
+++ /dev/null
-<div class="dataTables_wrapper">
- <div class="dataTables_header clearfix"
- *ngIf="toolHeader">
- <!-- actions -->
- <div class="oadatatableactions">
- <ng-content select="table-actions"></ng-content>
- </div>
- <!-- end actions -->
-
- <!-- search -->
- <div class="input-group">
- <span class="input-group-addon">
- <i class="glyphicon glyphicon-search"></i>
- </span>
- <input class="form-control"
- type="text"
- [(ngModel)]="search"
- (keyup)='updateFilter($event)'>
- <span class="input-group-btn">
- <button type="button"
- class="btn btn-default clear-input tc_clearInputBtn"
- (click)="updateFilter()">
- <i class="icon-prepend fa fa-remove"></i>
- </button>
- </span>
- </div>
- <!-- end search -->
-
- <!-- pagination limit -->
- <div class="input-group dataTables_paginate">
- <input class="form-control"
- type="number"
- min="1"
- max="9999"
- [value]="limit"
- (click)="setLimit($event)"
- (keyup)="setLimit($event)"
- (blur)="setLimit($event)">
- </div>
- <!-- end pagination limit-->
-
- <!-- refresh button -->
- <div class="widget-toolbar tc_refreshBtn">
- <a (click)="reloadData()">
- <i class="fa fa-lg fa-refresh"></i>
- </a>
- </div>
- <!-- end refresh button -->
- </div>
- <ngx-datatable #table
- class="bootstrap oadatatable"
- [cssClasses]="paginationClasses"
- [selectionType]="selectionType"
- [selected]="selected"
- (select)="toggleExpandRow()"
- [columns]="columns"
- [columnMode]="columnMode"
- [rows]="rows"
- [rowClass]="getRowClass()"
- [headerHeight]="header ? 'auto' : 0"
- [footerHeight]="footer ? 'auto' : 0"
- [limit]="limit > 0 ? limit : undefined"
- [loadingIndicator]="true"
- [rowHeight]="'auto'">
- <!-- Row Detail Template -->
- <ngx-datatable-row-detail (toggle)="updateDetailView()">
- </ngx-datatable-row-detail>
- </ngx-datatable>
-</div>
-<ng-template cdTableDetails></ng-template>
-<!-- cell templates that can be accessed from outside -->
-<ng-template #tableCellBoldTpl
- let-row="row"
- let-value="value">
- <strong>{{ value }}</strong>
-</ng-template>
+++ /dev/null
-@import '../../../../../defaults';
-
-.dataTables_wrapper {
- margin-bottom: 25px;
- .separator {
- height: 30px;
- border-left: 1px solid rgba(0,0,0,.09);
- padding-left: 5px;
- margin-left: 5px;
- display: inline-block;
- vertical-align: middle;
- }
- .widget-toolbar {
- display: inline-block;
- float: right;
- width: auto;
- height: 30px;
- line-height: 28px;
- position: relative;
- border-left: 1px solid rgba(0,0,0,.09);
- cursor: pointer;
- padding: 0 8px;
- text-align: center;
- }
- .dropdown-menu {
- white-space: nowrap;
- & > li {
- cursor: pointer;
- & > label {
- width: 100%;
- margin-bottom: 0;
- padding-left: 20px;
- padding-right: 20px;
- cursor: pointer;
- &:hover {
- background-color: #f5f5f5;
- }
- & > input {
- cursor: pointer;
- }
- }
- }
- }
- th.oadatatablecheckbox {
- width: 16px;
- }
- .dataTables_length>input {
- line-height: 25px;
- text-align: right;
- }
-}
-.dataTables_header {
- background-color: #f6f6f6;
- border: 1px solid #d1d1d1;
- border-bottom: none;
- padding: 5px;
- position: relative;
- .oadatatableactions {
- display: inline-block;
- }
- .input-group {
- float: right;
- border-left: 1px solid rgba(0,0,0,.09);
- padding-left: 8px;
- width: 40%;
- max-width: 350px;
- .form-control {
- height: 30px;
- }
- .clear-input {
- height: 30px;
- i {
- vertical-align: text-top;
- }
- }
- }
- .input-group.dataTables_paginate {
- width: 8%;
- }
-}
-
-::ng-deep .oadatatable {
- border: $border-color;
- margin-bottom: 0;
- max-width: none!important;
- .datatable-header {
- background-clip: padding-box;
- background-color: #f9f9f9;
- background-image: -webkit-linear-gradient(top,#fafafa 0,#ededed 100%);
- background-image: -o-linear-gradient(top,#fafafa 0,#ededed 100%);
- background-image: linear-gradient(to bottom,#fafafa 0,#ededed 100%);
- background-repeat: repeat-x;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0);
- .sort-asc, .sort-desc {
- color: $oa-color-blue;
- }
- .datatable-header-cell{
- @include table-cell;
- text-align: left;
- font-weight: bold;
- .datatable-header-cell-label {
- &:after {
- font-family: FontAwesome;
- font-weight: 400;
- height: 9px;
- left: 10px;
- line-height: 12px;
- position: relative;
- vertical-align: baseline;
- width: 12px;
- }
- }
- &.sortable {
- .datatable-header-cell-label:after {
- content: " \f0dc";
- }
- &.sort-active {
- &.sort-asc .datatable-header-cell-label:after {
- content: " \f160";
- }
- &.sort-desc .datatable-header-cell-label:after {
- content: " \f161";
- }
- }
- }
- &:first-child {
- border-left: none;
- }
- }
- }
- .datatable-body {
- .datatable-body-row {
- &.clickable:hover .datatable-row-group {
- background-color: #eee;
- transition-property: background;
- transition-duration: .3s;
- transition-timing-function: linear;
- }
- &.datatable-row-even {
- background-color: #ffffff;
- }
- &.datatable-row-odd {
- background-color: #f6f6f6;
- }
- &.active, &.active:hover {
- background-color: $bg-color-light-blue;
- }
- .datatable-body-cell{
- @include table-cell;
- &:first-child {
- border-left: none;
- }
- .datatable-body-cell-label {
- display: block;
- }
- }
- }
- }
- .datatable-footer {
- .selected-count, .page-count {
- font-style: italic;
- padding-left: 5px;
- }
- .datatable-pager .pager {
- margin-right: 5px;
- .pages {
- & > a, & > span {
- display: inline-block;
- padding: 5px 10px;
- margin-bottom: 5px;
- border: none;
- }
- a:hover {
- background-color: $oa-color-light-blue;
- }
- &.active > a {
- background-color: $bg-color-light-blue;
- }
- }
- }
- }
-}
+++ /dev/null
-import { async, ComponentFixture, TestBed } from '@angular/core/testing';
-import { FormsModule } from '@angular/forms';
-
-import { NgxDatatableModule, TableColumn } from '@swimlane/ngx-datatable';
-
-import { TableComponent } from './table.component';
-
-describe('TableComponent', () => {
- let component: TableComponent;
- let fixture: ComponentFixture<TableComponent>;
- const columns: TableColumn[] = [];
- const createFakeData = (n) => {
- const data = [];
- for (let i = 0; i < n; i++) {
- data.push({
- a: i,
- b: i * i,
- c: -(i % 10)
- });
- }
- return data;
- };
-
- beforeEach(
- async(() => {
- TestBed.configureTestingModule({
- declarations: [TableComponent],
- imports: [NgxDatatableModule, FormsModule]
- }).compileComponents();
- })
- );
-
- beforeEach(() => {
- fixture = TestBed.createComponent(TableComponent);
- component = fixture.componentInstance;
- });
-
- beforeEach(() => {
- component.data = createFakeData(100);
- component.useData();
- component.columns = [
- {prop: 'a'},
- {prop: 'b'},
- {prop: 'c'}
- ];
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-
- it('should have rows', () => {
- expect(component.data.length).toBe(100);
- expect(component.rows.length).toBe(component.data.length);
- });
-
- it('should have an int in setLimit parsing a string', () => {
- expect(component.limit).toBe(10);
- expect(component.limit).toEqual(jasmine.any(Number));
-
- const e = {target: {value: '1'}};
- component.setLimit(e);
- expect(component.limit).toBe(1);
- expect(component.limit).toEqual(jasmine.any(Number));
- e.target.value = '-20';
- component.setLimit(e);
- expect(component.limit).toBe(1);
- });
-
- it('should search for 13', () => {
- component.search = '13';
- expect(component.rows.length).toBe(100);
- component.updateFilter(true);
- expect(component.rows[0].a).toBe(13);
- expect(component.rows[1].b).toBe(1369);
- expect(component.rows[2].b).toBe(3136);
- expect(component.rows.length).toBe(3);
- });
-
- it('should restore full table after search', () => {
- component.search = '13';
- expect(component.rows.length).toBe(100);
- component.updateFilter(true);
- expect(component.rows.length).toBe(3);
- component.updateFilter();
- expect(component.rows.length).toBe(100);
- });
-});
+++ /dev/null
-import {
- AfterContentChecked,
- Component,
- ComponentFactoryResolver,
- EventEmitter,
- Input,
- OnChanges,
- OnInit,
- Output,
- TemplateRef,
- Type,
- ViewChild
-} from '@angular/core';
-
-import { DatatableComponent } from '@swimlane/ngx-datatable';
-import * as _ from 'lodash';
-
-import { CdTableColumn } from '../../../models/cd-table-column';
-import { TableDetailsDirective } from '../table-details.directive';
-
-@Component({
- selector: 'cd-table',
- templateUrl: './table.component.html',
- styleUrls: ['./table.component.scss']
-})
-export class TableComponent implements AfterContentChecked, OnInit, OnChanges {
- @ViewChild(DatatableComponent) table: DatatableComponent;
- @ViewChild(TableDetailsDirective) detailTemplate: TableDetailsDirective;
- @ViewChild('tableCellBoldTpl') tableCellBoldTpl: TemplateRef<any>;
-
- // This is the array with the items to be shown.
- @Input() data: any[];
- // Each item -> { prop: 'attribute name', name: 'display name' }
- @Input() columns: CdTableColumn[];
- // Method used for setting column widths.
- @Input() columnMode ?= 'force';
- // Name of the component e.g. 'TableDetailsComponent'
- @Input() detailsComponent?: string;
- // Display the tool header, including reload button, pagination and search fields?
- @Input() toolHeader ?= true;
- // Display the table header?
- @Input() header ?= true;
- // Display the table footer?
- @Input() footer ?= true;
- // Page size to show. Set to 0 to show unlimited number of rows.
- @Input() limit ?= 10;
- // An optional function that is called before the details page is show.
- // The current selection is passed as function argument. To do not display
- // the details page, return false.
- @Input() beforeShowDetails: Function;
- // Should be the function that will update the input data.
- @Output() fetchData = new EventEmitter();
-
- cellTemplates: {
- [key: string]: TemplateRef<any>
- } = {};
- selectionType: string = undefined;
- search = '';
- rows = [];
- selected = [];
- paginationClasses = {
- pagerLeftArrow: 'i fa fa-angle-double-left',
- pagerRightArrow: 'i fa fa-angle-double-right',
- pagerPrevious: 'i fa fa-angle-left',
- pagerNext: 'i fa fa-angle-right'
- };
-
- // Internal variable to check if it is necessary to recalculate the
- // table columns after the browser window has been resized.
- private currentWidth: number;
-
- constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
-
- ngOnInit() {
- this._addTemplates();
- this.columns.map((column) => {
- if (column.cellTransformation) {
- column.cellTemplate = this.cellTemplates[column.cellTransformation];
- }
- return column;
- });
- this.reloadData();
- if (this.detailsComponent) {
- this.selectionType = 'multi';
- }
- }
-
- ngAfterContentChecked() {
- // If the data table is not visible, e.g. another tab is active, and the
- // browser window gets resized, the table and its columns won't get resized
- // automatically if the tab gets visible again.
- // https://github.com/swimlane/ngx-datatable/issues/193
- // https://github.com/swimlane/ngx-datatable/issues/193#issuecomment-329144543
- if (this.table && this.table.element.clientWidth !== this.currentWidth) {
- this.currentWidth = this.table.element.clientWidth;
- // Force the redrawing of the table.
- window.dispatchEvent(new Event('resize'));
- }
- }
-
- _addTemplates () {
- this.cellTemplates.bold = this.tableCellBoldTpl;
- }
-
- ngOnChanges(changes) {
- this.useData();
- }
-
- setLimit(e) {
- const value = parseInt(e.target.value, 10);
- if (value > 0) {
- this.limit = value;
- }
- }
-
- reloadData() {
- this.fetchData.emit();
- }
-
- useData() {
- this.rows = [...this.data];
- }
-
- toggleExpandRow() {
- if (this.selected.length > 0) {
- this.table.rowDetail.toggleExpandRow(this.selected[0]);
- } else {
- this.detailTemplate.viewContainerRef.clear();
- }
- }
-
- updateDetailView() {
- if (!this.detailsComponent) {
- return;
- }
- if (_.isFunction(this.beforeShowDetails)) {
- if (!this.beforeShowDetails(this.selected)) {
- return;
- }
- }
- const factories = Array.from(this.componentFactoryResolver['_factories'].keys());
- const factoryClass = <Type<any>>factories.find((x: any) => x.name === this.detailsComponent);
- this.detailTemplate.viewContainerRef.clear();
- const cmpRef = this.detailTemplate.viewContainerRef.createComponent(
- this.componentFactoryResolver.resolveComponentFactory(factoryClass)
- );
- cmpRef.instance.selected = this.selected;
- }
-
- updateFilter(event?) {
- if (!event) {
- this.search = '';
- }
- const val = this.search.toLowerCase();
- const columns = this.columns;
- // update the rows
- this.rows = this.data.filter(function (d) {
- return columns.filter((c) => {
- return (typeof d[c.prop] === 'string' || typeof d[c.prop] === 'number')
- && (d[c.prop] + '').toLowerCase().indexOf(val) !== -1;
- }).length > 0;
- });
- // Whenever the filter changes, always go back to the first page
- this.table.offset = 0;
- }
-
- getRowClass() {
- // Return the function used to populate a row's CSS classes.
- return () => {
- return {
- 'clickable': !_.isUndefined(this.detailsComponent)
- };
- };
- }
-}
--- /dev/null
+import { CommonModule } from '@angular/common';
+import { NgModule } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+
+import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+
+import { ComponentsModule } from '../components/components.module';
+import { PipesModule } from '../pipes/pipes.module';
+import { TableDetailsDirective } from './table-details.directive';
+import { TableKeyValueComponent } from './table-key-value/table-key-value.component';
+import { TableComponent } from './table/table.component';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ NgxDatatableModule,
+ FormsModule,
+ PipesModule,
+ ComponentsModule
+ ],
+ declarations: [
+ TableComponent,
+ TableDetailsDirective,
+ TableKeyValueComponent
+ ],
+ exports: [
+ TableComponent,
+ NgxDatatableModule,
+ TableKeyValueComponent
+ ]
+})
+export class DataTableModule { }
--- /dev/null
+import { TableDetailsDirective } from './table-details.directive';
+
+describe('TableDetailsDirective', () => {
+ it('should create an instance', () => {
+ const directive = new TableDetailsDirective(null);
+ expect(directive).toBeTruthy();
+ });
+});
--- /dev/null
+import { Directive, Input, ViewContainerRef } from '@angular/core';
+
+@Directive({
+ selector: '[cdTableDetails]'
+})
+export class TableDetailsDirective {
+ @Input() selected?: any[];
+
+ constructor(public viewContainerRef: ViewContainerRef) { }
+
+}
--- /dev/null
+<cd-table [data]="data"
+ [columns]="columns"
+ columnMode="flex"
+ [toolHeader]="false"
+ [header]="false"
+ [footer]="false"
+ [limit]="0">
+</cd-table>
--- /dev/null
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule } from '@angular/forms';
+
+import { NgxDatatableModule } from '@swimlane/ngx-datatable';
+
+import { TableComponent } from '../table/table.component';
+import { TableKeyValueComponent } from './table-key-value.component';
+
+describe('TableKeyValueComponent', () => {
+ let component: TableKeyValueComponent;
+ let fixture: ComponentFixture<TableKeyValueComponent>;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ TableComponent, TableKeyValueComponent ],
+ imports: [ FormsModule, NgxDatatableModule ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TableKeyValueComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
--- /dev/null
+import { Component, Input, OnInit } from '@angular/core';
+
+import { CellTemplate } from '../../enum/cell-template.enum';
+import { CdTableColumn } from '../../models/cd-table-column';
+
+/**
+ * Display the given data in a 2 column data table. The left column
+ * shows the 'key' attribute, the right column the 'value' attribute.
+ * The data table has the following characteristics:
+ * - No header and footer is displayed
+ * - The relation of the width for the columns 'key' and 'value' is 1:3
+ * - The 'key' column is displayed in bold text
+ */
+@Component({
+ selector: 'cd-table-key-value',
+ templateUrl: './table-key-value.component.html',
+ styleUrls: ['./table-key-value.component.scss']
+})
+export class TableKeyValueComponent implements OnInit {
+
+ columns: Array<CdTableColumn> = [];
+
+ /**
+ * An array of objects to be displayed in the data table.
+ */
+ @Input() data: Array<object> = [];
+
+ /**
+ * The name of the attribute to be displayed as key.
+ * Defaults to 'key'.
+ * @type {string}
+ */
+ @Input() key = 'key';
+
+ /**
+ * The name of the attribute to be displayed as value.
+ * Defaults to 'value'.
+ * @type {string}
+ */
+ @Input() value = 'value';
+
+ constructor() { }
+
+ ngOnInit() {
+ this.columns = [
+ {
+ prop: this.key,
+ flexGrow: 1,
+ cellTransformation: CellTemplate.bold
+ },
+ {
+ prop: this.value,
+ flexGrow: 3
+ }
+ ];
+ }
+}
--- /dev/null
+<div class="dataTables_wrapper">
+ <div class="dataTables_header clearfix"
+ *ngIf="toolHeader">
+ <!-- actions -->
+ <div class="oadatatableactions">
+ <ng-content select="table-actions"></ng-content>
+ </div>
+ <!-- end actions -->
+
+ <!-- search -->
+ <div class="input-group">
+ <span class="input-group-addon">
+ <i class="glyphicon glyphicon-search"></i>
+ </span>
+ <input class="form-control"
+ type="text"
+ [(ngModel)]="search"
+ (keyup)='updateFilter($event)'>
+ <span class="input-group-btn">
+ <button type="button"
+ class="btn btn-default clear-input tc_clearInputBtn"
+ (click)="updateFilter()">
+ <i class="icon-prepend fa fa-remove"></i>
+ </button>
+ </span>
+ </div>
+ <!-- end search -->
+
+ <!-- pagination limit -->
+ <div class="input-group dataTables_paginate">
+ <input class="form-control"
+ type="number"
+ min="1"
+ max="9999"
+ [value]="limit"
+ (click)="setLimit($event)"
+ (keyup)="setLimit($event)"
+ (blur)="setLimit($event)">
+ </div>
+ <!-- end pagination limit-->
+
+ <!-- refresh button -->
+ <div class="widget-toolbar tc_refreshBtn">
+ <a (click)="reloadData()">
+ <i class="fa fa-lg fa-refresh"></i>
+ </a>
+ </div>
+ <!-- end refresh button -->
+ </div>
+ <ngx-datatable #table
+ class="bootstrap oadatatable"
+ [cssClasses]="paginationClasses"
+ [selectionType]="selectionType"
+ [selected]="selected"
+ (select)="toggleExpandRow()"
+ [columns]="columns"
+ [columnMode]="columnMode"
+ [rows]="rows"
+ [rowClass]="getRowClass()"
+ [headerHeight]="header ? 'auto' : 0"
+ [footerHeight]="footer ? 'auto' : 0"
+ [limit]="limit > 0 ? limit : undefined"
+ [loadingIndicator]="true"
+ [rowHeight]="'auto'">
+ <!-- Row Detail Template -->
+ <ngx-datatable-row-detail (toggle)="updateDetailView()">
+ </ngx-datatable-row-detail>
+ </ngx-datatable>
+</div>
+<ng-template cdTableDetails></ng-template>
+<!-- cell templates that can be accessed from outside -->
+<ng-template #tableCellBoldTpl
+ let-row="row"
+ let-value="value">
+ <strong>{{ value }}</strong>
+</ng-template>
--- /dev/null
+@import '../../../../defaults';
+
+.dataTables_wrapper {
+ margin-bottom: 25px;
+ .separator {
+ height: 30px;
+ border-left: 1px solid rgba(0,0,0,.09);
+ padding-left: 5px;
+ margin-left: 5px;
+ display: inline-block;
+ vertical-align: middle;
+ }
+ .widget-toolbar {
+ display: inline-block;
+ float: right;
+ width: auto;
+ height: 30px;
+ line-height: 28px;
+ position: relative;
+ border-left: 1px solid rgba(0,0,0,.09);
+ cursor: pointer;
+ padding: 0 8px;
+ text-align: center;
+ }
+ .dropdown-menu {
+ white-space: nowrap;
+ & > li {
+ cursor: pointer;
+ & > label {
+ width: 100%;
+ margin-bottom: 0;
+ padding-left: 20px;
+ padding-right: 20px;
+ cursor: pointer;
+ &:hover {
+ background-color: #f5f5f5;
+ }
+ & > input {
+ cursor: pointer;
+ }
+ }
+ }
+ }
+ th.oadatatablecheckbox {
+ width: 16px;
+ }
+ .dataTables_length>input {
+ line-height: 25px;
+ text-align: right;
+ }
+}
+.dataTables_header {
+ background-color: #f6f6f6;
+ border: 1px solid #d1d1d1;
+ border-bottom: none;
+ padding: 5px;
+ position: relative;
+ .oadatatableactions {
+ display: inline-block;
+ }
+ .input-group {
+ float: right;
+ border-left: 1px solid rgba(0,0,0,.09);
+ padding-left: 8px;
+ width: 40%;
+ max-width: 350px;
+ .form-control {
+ height: 30px;
+ }
+ .clear-input {
+ height: 30px;
+ i {
+ vertical-align: text-top;
+ }
+ }
+ }
+ .input-group.dataTables_paginate {
+ width: 8%;
+ }
+}
+
+::ng-deep .oadatatable {
+ border: $border-color;
+ margin-bottom: 0;
+ max-width: none!important;
+ .datatable-header {
+ background-clip: padding-box;
+ background-color: #f9f9f9;
+ background-image: -webkit-linear-gradient(top,#fafafa 0,#ededed 100%);
+ background-image: -o-linear-gradient(top,#fafafa 0,#ededed 100%);
+ background-image: linear-gradient(to bottom,#fafafa 0,#ededed 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffafafa', endColorstr='#ffededed', GradientType=0);
+ .sort-asc, .sort-desc {
+ color: $oa-color-blue;
+ }
+ .datatable-header-cell{
+ @include table-cell;
+ text-align: left;
+ font-weight: bold;
+ .datatable-header-cell-label {
+ &:after {
+ font-family: FontAwesome;
+ font-weight: 400;
+ height: 9px;
+ left: 10px;
+ line-height: 12px;
+ position: relative;
+ vertical-align: baseline;
+ width: 12px;
+ }
+ }
+ &.sortable {
+ .datatable-header-cell-label:after {
+ content: " \f0dc";
+ }
+ &.sort-active {
+ &.sort-asc .datatable-header-cell-label:after {
+ content: " \f160";
+ }
+ &.sort-desc .datatable-header-cell-label:after {
+ content: " \f161";
+ }
+ }
+ }
+ &:first-child {
+ border-left: none;
+ }
+ }
+ }
+ .datatable-body {
+ .datatable-body-row {
+ &.clickable:hover .datatable-row-group {
+ background-color: #eee;
+ transition-property: background;
+ transition-duration: .3s;
+ transition-timing-function: linear;
+ }
+ &.datatable-row-even {
+ background-color: #ffffff;
+ }
+ &.datatable-row-odd {
+ background-color: #f6f6f6;
+ }
+ &.active, &.active:hover {
+ background-color: $bg-color-light-blue;
+ }
+ .datatable-body-cell{
+ @include table-cell;
+ &:first-child {
+ border-left: none;
+ }
+ .datatable-body-cell-label {
+ display: block;
+ }
+ }
+ }
+ }
+ .datatable-footer {
+ .selected-count, .page-count {
+ font-style: italic;
+ padding-left: 5px;
+ }
+ .datatable-pager .pager {
+ margin-right: 5px;
+ .pages {
+ & > a, & > span {
+ display: inline-block;
+ padding: 5px 10px;
+ margin-bottom: 5px;
+ border: none;
+ }
+ a:hover {
+ background-color: $oa-color-light-blue;
+ }
+ &.active > a {
+ background-color: $bg-color-light-blue;
+ }
+ }
+ }
+ }
+}
--- /dev/null
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { FormsModule } from '@angular/forms';
+
+import { NgxDatatableModule, TableColumn } from '@swimlane/ngx-datatable';
+
+import { TableComponent } from './table.component';
+
+describe('TableComponent', () => {
+ let component: TableComponent;
+ let fixture: ComponentFixture<TableComponent>;
+ const columns: TableColumn[] = [];
+ const createFakeData = (n) => {
+ const data = [];
+ for (let i = 0; i < n; i++) {
+ data.push({
+ a: i,
+ b: i * i,
+ c: -(i % 10)
+ });
+ }
+ return data;
+ };
+
+ beforeEach(
+ async(() => {
+ TestBed.configureTestingModule({
+ declarations: [TableComponent],
+ imports: [NgxDatatableModule, FormsModule]
+ }).compileComponents();
+ })
+ );
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(TableComponent);
+ component = fixture.componentInstance;
+ });
+
+ beforeEach(() => {
+ component.data = createFakeData(100);
+ component.useData();
+ component.columns = [
+ {prop: 'a'},
+ {prop: 'b'},
+ {prop: 'c'}
+ ];
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should have rows', () => {
+ expect(component.data.length).toBe(100);
+ expect(component.rows.length).toBe(component.data.length);
+ });
+
+ it('should have an int in setLimit parsing a string', () => {
+ expect(component.limit).toBe(10);
+ expect(component.limit).toEqual(jasmine.any(Number));
+
+ const e = {target: {value: '1'}};
+ component.setLimit(e);
+ expect(component.limit).toBe(1);
+ expect(component.limit).toEqual(jasmine.any(Number));
+ e.target.value = '-20';
+ component.setLimit(e);
+ expect(component.limit).toBe(1);
+ });
+
+ it('should search for 13', () => {
+ component.search = '13';
+ expect(component.rows.length).toBe(100);
+ component.updateFilter(true);
+ expect(component.rows[0].a).toBe(13);
+ expect(component.rows[1].b).toBe(1369);
+ expect(component.rows[2].b).toBe(3136);
+ expect(component.rows.length).toBe(3);
+ });
+
+ it('should restore full table after search', () => {
+ component.search = '13';
+ expect(component.rows.length).toBe(100);
+ component.updateFilter(true);
+ expect(component.rows.length).toBe(3);
+ component.updateFilter();
+ expect(component.rows.length).toBe(100);
+ });
+});
--- /dev/null
+import {
+ AfterContentChecked,
+ Component,
+ ComponentFactoryResolver,
+ EventEmitter,
+ Input,
+ OnChanges,
+ OnInit,
+ Output,
+ TemplateRef,
+ Type,
+ ViewChild
+} from '@angular/core';
+
+import { DatatableComponent } from '@swimlane/ngx-datatable';
+import * as _ from 'lodash';
+
+import { CdTableColumn } from '../../models/cd-table-column';
+import { TableDetailsDirective } from '../table-details.directive';
+
+@Component({
+ selector: 'cd-table',
+ templateUrl: './table.component.html',
+ styleUrls: ['./table.component.scss']
+})
+export class TableComponent implements AfterContentChecked, OnInit, OnChanges {
+ @ViewChild(DatatableComponent) table: DatatableComponent;
+ @ViewChild(TableDetailsDirective) detailTemplate: TableDetailsDirective;
+ @ViewChild('tableCellBoldTpl') tableCellBoldTpl: TemplateRef<any>;
+
+ // This is the array with the items to be shown.
+ @Input() data: any[];
+ // Each item -> { prop: 'attribute name', name: 'display name' }
+ @Input() columns: CdTableColumn[];
+ // Method used for setting column widths.
+ @Input() columnMode ?= 'force';
+ // Name of the component e.g. 'TableDetailsComponent'
+ @Input() detailsComponent?: string;
+ // Display the tool header, including reload button, pagination and search fields?
+ @Input() toolHeader ?= true;
+ // Display the table header?
+ @Input() header ?= true;
+ // Display the table footer?
+ @Input() footer ?= true;
+ // Page size to show. Set to 0 to show unlimited number of rows.
+ @Input() limit ?= 10;
+ // An optional function that is called before the details page is show.
+ // The current selection is passed as function argument. To do not display
+ // the details page, return false.
+ @Input() beforeShowDetails: Function;
+ // Should be the function that will update the input data.
+ @Output() fetchData = new EventEmitter();
+
+ cellTemplates: {
+ [key: string]: TemplateRef<any>
+ } = {};
+ selectionType: string = undefined;
+ search = '';
+ rows = [];
+ selected = [];
+ paginationClasses = {
+ pagerLeftArrow: 'i fa fa-angle-double-left',
+ pagerRightArrow: 'i fa fa-angle-double-right',
+ pagerPrevious: 'i fa fa-angle-left',
+ pagerNext: 'i fa fa-angle-right'
+ };
+
+ // Internal variable to check if it is necessary to recalculate the
+ // table columns after the browser window has been resized.
+ private currentWidth: number;
+
+ constructor(private componentFactoryResolver: ComponentFactoryResolver) { }
+
+ ngOnInit() {
+ this._addTemplates();
+ this.columns.map((column) => {
+ if (column.cellTransformation) {
+ column.cellTemplate = this.cellTemplates[column.cellTransformation];
+ }
+ return column;
+ });
+ this.reloadData();
+ if (this.detailsComponent) {
+ this.selectionType = 'multi';
+ }
+ }
+
+ ngAfterContentChecked() {
+ // If the data table is not visible, e.g. another tab is active, and the
+ // browser window gets resized, the table and its columns won't get resized
+ // automatically if the tab gets visible again.
+ // https://github.com/swimlane/ngx-datatable/issues/193
+ // https://github.com/swimlane/ngx-datatable/issues/193#issuecomment-329144543
+ if (this.table && this.table.element.clientWidth !== this.currentWidth) {
+ this.currentWidth = this.table.element.clientWidth;
+ // Force the redrawing of the table.
+ window.dispatchEvent(new Event('resize'));
+ }
+ }
+
+ _addTemplates () {
+ this.cellTemplates.bold = this.tableCellBoldTpl;
+ }
+
+ ngOnChanges(changes) {
+ this.useData();
+ }
+
+ setLimit(e) {
+ const value = parseInt(e.target.value, 10);
+ if (value > 0) {
+ this.limit = value;
+ }
+ }
+
+ reloadData() {
+ this.fetchData.emit();
+ }
+
+ useData() {
+ this.rows = [...this.data];
+ }
+
+ toggleExpandRow() {
+ if (this.selected.length > 0) {
+ this.table.rowDetail.toggleExpandRow(this.selected[0]);
+ } else {
+ this.detailTemplate.viewContainerRef.clear();
+ }
+ }
+
+ updateDetailView() {
+ if (!this.detailsComponent) {
+ return;
+ }
+ if (_.isFunction(this.beforeShowDetails)) {
+ if (!this.beforeShowDetails(this.selected)) {
+ return;
+ }
+ }
+ const factories = Array.from(this.componentFactoryResolver['_factories'].keys());
+ const factoryClass = <Type<any>>factories.find((x: any) => x.name === this.detailsComponent);
+ this.detailTemplate.viewContainerRef.clear();
+ const cmpRef = this.detailTemplate.viewContainerRef.createComponent(
+ this.componentFactoryResolver.resolveComponentFactory(factoryClass)
+ );
+ cmpRef.instance.selected = this.selected;
+ }
+
+ updateFilter(event?) {
+ if (!event) {
+ this.search = '';
+ }
+ const val = this.search.toLowerCase();
+ const columns = this.columns;
+ // update the rows
+ this.rows = this.data.filter(function (d) {
+ return columns.filter((c) => {
+ return (typeof d[c.prop] === 'string' || typeof d[c.prop] === 'number')
+ && (d[c.prop] + '').toLowerCase().indexOf(val) !== -1;
+ }).length > 0;
+ });
+ // Whenever the filter changes, always go back to the first page
+ this.table.offset = 0;
+ }
+
+ getRowClass() {
+ // Return the function used to populate a row's CSS classes.
+ return () => {
+ return {
+ 'clickable': !_.isUndefined(this.detailsComponent)
+ };
+ };
+ }
+}
import { NgModule } from '@angular/core';
import { ComponentsModule } from './components/components.module';
+import { DataTableModule } from './datatable/datatable.module';
import { PasswordButtonDirective } from './directives/password-button.directive';
import { PipesModule } from './pipes/pipes.module';
import { AuthGuardService } from './services/auth-guard.service';
CommonModule,
PipesModule,
ComponentsModule,
- ServicesModule
+ ServicesModule,
+ DataTableModule
],
exports: [
PipesModule,
ComponentsModule,
ServicesModule,
PasswordButtonDirective,
- ComponentsModule
+ ComponentsModule,
+ DataTableModule
],
declarations: [
PasswordButtonDirective