+
-
- more_vert
-
-
-
- {{ action.icon }}
- {{ action.title | translate }}
+
+
+
+ more_vert
-
+
+
+ {{ action.icon }}
+ {{ action.title | translate }}
+
+
+
{
expect(headerCells[1].innerText).toBe(dataTableSchema[0].title);
});
});
+
+describe('Show/hide columns', () => {
+ let fixture: ComponentFixture
;
+ let dataTable: DataTableComponent;
+ let data: DataColumn[] = [];
+ let dataTableSchema: DataColumn[] = [];
+
+ setupTestBed({
+ imports: [
+ TranslateModule.forRoot(),
+ CoreTestingModule
+ ],
+ declarations: [CustomColumnTemplateComponent],
+ schemas: [NO_ERRORS_SCHEMA]
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DataTableComponent);
+ dataTable = fixture.componentInstance;
+ data = [
+ { id: '1', title: 'name1', key: 'key', type: 'text' },
+ { id: '2', title: 'name1', key: 'key', type: 'text' },
+ { id: '3', title: 'name1', key: 'key', type: 'text' }
+ ];
+
+ dataTableSchema = [
+ new ObjectDataColumn({ key: 'id', title: 'ID' }),
+ new ObjectDataColumn({ key: 'name', title: 'Name'}),
+ new ObjectDataColumn({ key: 'status', title: 'status', isHidden: true })
+ ];
+
+ dataTable.data = new ObjectDataTableAdapter(
+ [...data],
+ [...dataTableSchema]
+ );
+
+ fixture.detectChanges();
+ });
+
+ it('should hide columns with isHidden prop', () => {
+ const headerCells = fixture.debugElement.nativeElement.querySelectorAll('.adf-datatable-cell--text.adf-datatable-cell-header');
+
+ expect(headerCells.length).toBe(2);
+ });
+
+ it('should reload columns after changing columns visibility', () => {
+ const columns = [
+ new ObjectDataColumn({ key: 'id', title: 'ID' }),
+ new ObjectDataColumn({ key: 'name', title: 'Name', isHidden: true }),
+ new ObjectDataColumn({ key: 'status', title: 'status', isHidden: true })
+ ];
+
+ dataTable.ngOnChanges({
+ columns: {
+ previousValue: undefined,
+ currentValue: columns,
+ firstChange: false,
+ isFirstChange: () => false
+ }
+ });
+
+ fixture.detectChanges();
+
+ const headerCells = fixture.debugElement.nativeElement.querySelectorAll('.adf-datatable-cell--text.adf-datatable-cell-header');
+ expect(headerCells.length).toBe(1);
+ });
+});
diff --git a/lib/core/datatable/components/datatable/datatable.component.ts b/lib/core/datatable/components/datatable/datatable.component.ts
index 461407709d..dc45e39595 100644
--- a/lib/core/datatable/components/datatable/datatable.component.ts
+++ b/lib/core/datatable/components/datatable/datatable.component.ts
@@ -104,6 +104,10 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
@Input()
multiselect: boolean = false;
+ /** Toggles main data table action column. */
+ @Input()
+ mainTableAction: boolean = true;
+
/** Toggles the data actions column. */
@Input()
actions: boolean = false;
@@ -200,6 +204,7 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
noContentTemplate: TemplateRef;
noPermissionTemplate: TemplateRef;
loadingTemplate: TemplateRef;
+ mainActionTemplate: TemplateRef;
isSelectAllIndeterminate: boolean = false;
isSelectAllChecked: boolean = false;
@@ -313,10 +318,16 @@ export class DataTableComponent implements OnInit, AfterContentInit, OnChanges,
}
onDropHeaderColumn(event: CdkDragDrop): void {
- const columns = this.data.getColumns();
- moveItemInArray(columns, event.previousIndex, event.currentIndex);
+ const allColumns = this.data.getColumns();
+ const shownColumns = allColumns.filter(column => !column.isHidden);
+ const hiddenColumns = allColumns.filter(column => column.isHidden);
+
+ moveItemInArray(shownColumns, event.previousIndex, event.currentIndex);
+ const allColumnsWithNewOrder = [...shownColumns, ...hiddenColumns];
+
+ this.setTableColumns(allColumnsWithNewOrder);
+ this.columnOrderChanged.emit(allColumnsWithNewOrder);
- this.columnOrderChanged.emit(columns);
this.isDraggingHeaderColumn = false;
}
diff --git a/lib/core/datatable/data/data-column.model.ts b/lib/core/datatable/data/data-column.model.ts
index 3eff6ed68a..cbbbe3ee3b 100644
--- a/lib/core/datatable/data/data-column.model.ts
+++ b/lib/core/datatable/data/data-column.model.ts
@@ -46,4 +46,5 @@ export interface DataColumn {
sortingKey?: string;
header?: TemplateRef;
draggable?: boolean;
+ isHidden?: boolean;
}
diff --git a/lib/core/datatable/data/data-table.schema.ts b/lib/core/datatable/data/data-table.schema.ts
index 7a5d4d1ca0..bdae7d78de 100644
--- a/lib/core/datatable/data/data-table.schema.ts
+++ b/lib/core/datatable/data/data-table.schema.ts
@@ -38,6 +38,9 @@ export abstract class DataTableSchema {
protected columnsOrder: string[] | undefined;
protected columnsOrderedByKey: string = 'id';
+ protected hiddenColumns: string[] | undefined;
+ protected hiddenColumnsKey: string = 'id';
+
private layoutPresets = {};
private columnsSchemaSubject$ = new ReplaySubject();
@@ -59,7 +62,8 @@ export abstract class DataTableSchema {
}
public createColumns(): void {
- const columns = this.mergeJsonAndHtmlSchema();
+ const allColumns = this.mergeJsonAndHtmlSchema();
+ const columns = this.setHiddenColumns(allColumns);
this.columns = this.sortColumnsByKey(columns);
}
@@ -127,4 +131,19 @@ export abstract class DataTableSchema {
return [...columnsWithProperOrder, ...defaultColumns];
}
+
+ private setHiddenColumns(columns: DataColumn[]): DataColumn[] {
+ if (this.hiddenColumns) {
+ return columns.map(column => {
+ const columnShouldBeHidden = this.hiddenColumns.includes(column[this.hiddenColumnsKey]);
+
+ return {
+ ...column,
+ isHidden: columnShouldBeHidden
+ };
+ });
+ }
+
+ return columns;
+ }
}
diff --git a/lib/core/datatable/data/object-datacolumn.model.ts b/lib/core/datatable/data/object-datacolumn.model.ts
index 075c2a3cb1..b4d6d9151d 100644
--- a/lib/core/datatable/data/object-datacolumn.model.ts
+++ b/lib/core/datatable/data/object-datacolumn.model.ts
@@ -34,6 +34,7 @@ export class ObjectDataColumn implements DataColumn {
sortingKey?: string;
header?: TemplateRef;
draggable: boolean;
+ isHidden: boolean;
constructor(input: any) {
this.id = input.id ?? '';
@@ -50,5 +51,6 @@ export class ObjectDataColumn implements DataColumn {
this.sortingKey = input.sortingKey;
this.header = input.header;
this.draggable = input.draggable ?? false;
+ this.isHidden = input.isHidden ?? false;
}
}
diff --git a/lib/core/datatable/datatable.module.ts b/lib/core/datatable/datatable.module.ts
index d4a9e2f43a..1d70aacd78 100644
--- a/lib/core/datatable/datatable.module.ts
+++ b/lib/core/datatable/datatable.module.ts
@@ -29,6 +29,7 @@ import { DataTableCellComponent } from './components/datatable-cell/datatable-ce
import { DataTableRowComponent } from './components/datatable-row/datatable-row.component';
import { DataTableComponent } from './components/datatable/datatable.component';
import { DateCellComponent } from './components/date-cell/date-cell.component';
+import { ColumnsSelectorComponent } from './components/columns-selector/columns-selector.component';
import { EmptyListBodyDirective,
EmptyListComponent,
EmptyListFooterDirective,
@@ -42,12 +43,14 @@ import { HeaderFilterTemplateDirective } from './directives/header-filter-templa
import { CustomEmptyContentTemplateDirective } from './directives/custom-empty-content-template.directive';
import { CustomLoadingContentTemplateDirective } from './directives/custom-loading-template.directive';
import { CustomNoPermissionTemplateDirective } from './directives/custom-no-permission-template.directive';
+import { MainMenuDataTableTemplateDirective } from './directives/main-data-table-action-template.directive';
import { JsonCellComponent } from './components/json-cell/json-cell.component';
import { ClipboardModule } from '../clipboard/clipboard.module';
import { DropZoneDirective } from './directives/drop-zone.directive';
import { DataColumnModule } from '../data-column/data-column.module';
import { DragDropModule } from '@angular/cdk/drag-drop';
import { IconModule } from '../icon/icon.module';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@NgModule({
imports: [
@@ -61,7 +64,9 @@ import { IconModule } from '../icon/icon.module';
DirectiveModule,
ClipboardModule,
DragDropModule,
- IconModule
+ IconModule,
+ FormsModule,
+ ReactiveFormsModule
],
declarations: [
DataTableComponent,
@@ -75,6 +80,7 @@ import { IconModule } from '../icon/icon.module';
FileSizeCellComponent,
LocationCellComponent,
JsonCellComponent,
+ ColumnsSelectorComponent,
NoContentTemplateDirective,
NoPermissionTemplateDirective,
LoadingContentTemplateDirective,
@@ -82,6 +88,7 @@ import { IconModule } from '../icon/icon.module';
CustomEmptyContentTemplateDirective,
CustomLoadingContentTemplateDirective,
CustomNoPermissionTemplateDirective,
+ MainMenuDataTableTemplateDirective,
DropZoneDirective
],
exports: [
@@ -93,6 +100,7 @@ import { IconModule } from '../icon/icon.module';
DataTableCellComponent,
DataTableRowComponent,
DateCellComponent,
+ ColumnsSelectorComponent,
FileSizeCellComponent,
LocationCellComponent,
JsonCellComponent,
@@ -103,6 +111,7 @@ import { IconModule } from '../icon/icon.module';
CustomEmptyContentTemplateDirective,
CustomLoadingContentTemplateDirective,
CustomNoPermissionTemplateDirective,
+ MainMenuDataTableTemplateDirective,
DropZoneDirective
]
})
diff --git a/lib/core/datatable/directives/main-data-table-action-template.directive.spec.ts b/lib/core/datatable/directives/main-data-table-action-template.directive.spec.ts
new file mode 100644
index 0000000000..44c9b468a1
--- /dev/null
+++ b/lib/core/datatable/directives/main-data-table-action-template.directive.spec.ts
@@ -0,0 +1,44 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { TestBed, ComponentFixture } from '@angular/core/testing';
+import { DataTableComponent } from '../components/datatable/datatable.component';
+import { MainMenuDataTableTemplateDirective } from './main-data-table-action-template.directive';
+
+describe('MainMenuDataTableTemplateDirective', () => {
+
+ let fixture: ComponentFixture;
+ let dataTable: DataTableComponent;
+ let directive: MainMenuDataTableTemplateDirective;
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(DataTableComponent);
+ dataTable = fixture.componentInstance;
+ directive = new MainMenuDataTableTemplateDirective(dataTable);
+ });
+
+ afterEach(() => {
+ fixture.destroy();
+ });
+
+ it('applies template to the datatable', () => {
+ const template: any = 'test template';
+ directive.template = template;
+ directive.ngAfterContentInit();
+ expect(dataTable.mainActionTemplate).toBe(template);
+ });
+});
diff --git a/lib/core/datatable/directives/main-data-table-action-template.directive.ts b/lib/core/datatable/directives/main-data-table-action-template.directive.ts
new file mode 100644
index 0000000000..1e5a97b333
--- /dev/null
+++ b/lib/core/datatable/directives/main-data-table-action-template.directive.ts
@@ -0,0 +1,36 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { AfterContentInit, ContentChild, Directive, TemplateRef } from '@angular/core';
+import { DataTableComponent } from '../components/datatable/datatable.component';
+
+@Directive({
+ selector: 'adf-main-menu-datatable-template'
+})
+export class MainMenuDataTableTemplateDirective implements AfterContentInit {
+
+ @ContentChild(TemplateRef)
+ template: any;
+
+ constructor(private dataTable: DataTableComponent) {}
+
+ ngAfterContentInit() {
+ if (this.dataTable) {
+ this.dataTable.mainActionTemplate = this.template;
+ }
+ }
+}
diff --git a/lib/core/datatable/public-api.ts b/lib/core/datatable/public-api.ts
index 533de02367..0ea838113b 100644
--- a/lib/core/datatable/public-api.ts
+++ b/lib/core/datatable/public-api.ts
@@ -38,6 +38,7 @@ export * from './components/empty-list/empty-list.component';
export * from './components/filesize-cell/filesize-cell.component';
export * from './components/json-cell/json-cell.component';
export * from './components/location-cell/location-cell.component';
+export * from './components/columns-selector/columns-selector.component';
export * from './data/data-table.schema';
export * from './directives/loading-template.directive';
diff --git a/lib/core/i18n/en.json b/lib/core/i18n/en.json
index 276e84da08..de999306a7 100644
--- a/lib/core/i18n/en.json
+++ b/lib/core/i18n/en.json
@@ -350,6 +350,11 @@
"PRESENTATION": "Presentation",
"SPREADSHEET": "Spreadsheet",
"MISCELLANEOUS": "Miscellaneous"
+ },
+ "COLUMNS_SELECTOR": {
+ "COLUMNS": "Columns",
+ "SEARCH": "Search",
+ "APPLY": "Apply"
}
},
"USER_PROFILE": {
diff --git a/lib/core/pipes/filter-out-every-object-by-prop.pipe.spec.ts b/lib/core/pipes/filter-out-every-object-by-prop.pipe.spec.ts
new file mode 100644
index 0000000000..ca7bfd7247
--- /dev/null
+++ b/lib/core/pipes/filter-out-every-object-by-prop.pipe.spec.ts
@@ -0,0 +1,75 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { FilterOutArrayObjectsByPropPipe } from './filter-out-every-object-by-prop.pipe';
+
+describe('FilterOutArrayObjectsByPropPipe', () => {
+ let pipe: FilterOutArrayObjectsByPropPipe;
+
+ beforeEach(() => {
+ pipe = new FilterOutArrayObjectsByPropPipe();
+ });
+
+ it('should filter out object', () => {
+ const testArray = [{
+ id: 1
+ }, {
+ id: 2
+ }, {
+ id: 3
+ }];
+
+ const result = pipe.transform(testArray, 'id', 3);
+
+ expect(result.length).toBe(testArray.length - 1);
+ expect(result[0]).toEqual(testArray[0]);
+ expect(result[1]).toEqual(testArray[1]);
+ });
+
+ it('should filter out multiple objects', () => {
+ const testArray = [{
+ isHidden: true
+ }, {
+ isHidden: true
+ }, {
+ isHidden: true
+ }, {
+ isHidden: false
+ }];
+
+ const result = pipe.transform(testArray, 'isHidden', true);
+
+ expect(result.length).toBe(1);
+ expect(result[0]).toEqual(testArray[3]);
+ });
+
+ it('should work with empty array', () => {
+ const testArray = [];
+
+ const result = pipe.transform(testArray, 'prop', true);
+
+ expect(result.length).toBe(0);
+ });
+
+ it('should work with non existing prop', () => {
+ const testArray = [{ prop: 1 }];
+
+ const result = pipe.transform(testArray, 'nonExistionProp', 1);
+
+ expect(result.length).toBe(1);
+ });
+});
diff --git a/lib/core/pipes/filter-out-every-object-by-prop.pipe.ts b/lib/core/pipes/filter-out-every-object-by-prop.pipe.ts
new file mode 100644
index 0000000000..2154a90711
--- /dev/null
+++ b/lib/core/pipes/filter-out-every-object-by-prop.pipe.ts
@@ -0,0 +1,25 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({ name: 'filterOutEvery' })
+export class FilterOutArrayObjectsByPropPipe implements PipeTransform {
+ transform(values: T[], filterKey: string, filterValue: any): T[] {
+ return (values ?? []).filter(value => value[filterKey] !== filterValue);
+ }
+}
diff --git a/lib/core/pipes/filter-string.pipe.spec.ts b/lib/core/pipes/filter-string.pipe.spec.ts
new file mode 100644
index 0000000000..380a79afbd
--- /dev/null
+++ b/lib/core/pipes/filter-string.pipe.spec.ts
@@ -0,0 +1,44 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { FilterStringPipe } from './filter-string.pipe';
+
+describe('FilterStringPipe', () => {
+ let pipe: FilterStringPipe;
+
+ beforeEach(() => {
+ pipe = new FilterStringPipe();
+ });
+
+ it('should left string', () => {
+ const result = pipe.transform('ABC', 'B');
+
+ expect(result).toBe('ABC');
+ });
+
+ it('should filter out string', () => {
+ const result = pipe.transform('ABC', 'D');
+
+ expect(result).toBe('');
+ });
+
+ it('should left string when no query string is passed', () => {
+ const result = pipe.transform('ABC');
+
+ expect(result).toBe('ABC');
+ });
+});
diff --git a/lib/core/pipes/filter-string.pipe.ts b/lib/core/pipes/filter-string.pipe.ts
new file mode 100644
index 0000000000..8b2725a50f
--- /dev/null
+++ b/lib/core/pipes/filter-string.pipe.ts
@@ -0,0 +1,30 @@
+/*!
+ * @license
+ * Copyright 2019 Alfresco Software, Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { Pipe, PipeTransform } from '@angular/core';
+
+@Pipe({ name: 'filterString' })
+export class FilterStringPipe implements PipeTransform {
+
+ transform(value: string = '', filterBy: string = ''): string {
+ const testResult = filterBy ?
+ value.toLowerCase().indexOf(filterBy.toLowerCase()) > -1 :
+ true;
+
+ return testResult ? value : '';
+ }
+}
diff --git a/lib/core/pipes/pipe.module.ts b/lib/core/pipes/pipe.module.ts
index 0ac38f822e..de11fb9731 100644
--- a/lib/core/pipes/pipe.module.ts
+++ b/lib/core/pipes/pipe.module.ts
@@ -34,6 +34,8 @@ import { LocalizedRolePipe } from './localized-role.pipe';
import { TranslateModule } from '@ngx-translate/core';
import { MomentDatePipe } from './moment-date.pipe';
import { MomentDateTimePipe } from './moment-datetime.pipe';
+import { FilterStringPipe } from './filter-string.pipe';
+import { FilterOutArrayObjectsByPropPipe } from './filter-out-every-object-by-prop.pipe';
@NgModule({
imports: [
@@ -55,7 +57,9 @@ import { MomentDateTimePipe } from './moment-datetime.pipe';
DecimalNumberPipe,
LocalizedRolePipe,
MomentDatePipe,
- MomentDateTimePipe
+ MomentDateTimePipe,
+ FilterStringPipe,
+ FilterOutArrayObjectsByPropPipe
],
providers: [
FileSizePipe,
@@ -71,7 +75,9 @@ import { MomentDateTimePipe } from './moment-datetime.pipe';
DecimalNumberPipe,
LocalizedRolePipe,
MomentDatePipe,
- MomentDateTimePipe
+ MomentDateTimePipe,
+ FilterStringPipe,
+ FilterOutArrayObjectsByPropPipe
],
exports: [
FileSizePipe,
@@ -88,7 +94,9 @@ import { MomentDateTimePipe } from './moment-datetime.pipe';
DecimalNumberPipe,
LocalizedRolePipe,
MomentDatePipe,
- MomentDateTimePipe
+ MomentDateTimePipe,
+ FilterStringPipe,
+ FilterOutArrayObjectsByPropPipe
]
})
export class PipeModule {
diff --git a/lib/core/pipes/public-api.ts b/lib/core/pipes/public-api.ts
index 1fd17a9f0e..449abb0baa 100644
--- a/lib/core/pipes/public-api.ts
+++ b/lib/core/pipes/public-api.ts
@@ -31,3 +31,5 @@ export * from './localized-role.pipe';
export * from './pipe.module';
export * from './moment-date.pipe';
export * from './moment-datetime.pipe';
+export * from './filter-string.pipe';
+export * from './filter-out-every-object-by-prop.pipe';