saving as CSV implemented (user list)
This commit is contained in:
parent
8aca83a6bf
commit
86b6296205
@ -29,6 +29,7 @@
|
||||
"@ngx-translate/core": "^10.0.2",
|
||||
"@ngx-translate/http-loader": "^3.0.1",
|
||||
"core-js": "^2.5.4",
|
||||
"file-saver": "^2.0.0-rc.3",
|
||||
"material-design-icons": "^3.0.1",
|
||||
"ngx-mat-select-search": "^1.4.0",
|
||||
"roboto-fontface": "^0.10.0",
|
||||
|
@ -54,6 +54,7 @@ export class AppComponent {
|
||||
* TODO: Overloading can be extended to more functions.
|
||||
*/
|
||||
private overloadArrayToString(): void {
|
||||
|
||||
Array.prototype.toString = function(): string {
|
||||
let string = '';
|
||||
const iterations = Math.min(this.length, 3);
|
||||
|
17
client/src/app/core/services/csv-export.service.spec.ts
Normal file
17
client/src/app/core/services/csv-export.service.spec.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { CsvExportService } from './csv-export.service';
|
||||
import { E2EImportsModule } from '../../../e2e-imports.module';
|
||||
|
||||
describe('CsvExportService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
providers: [CsvExportService]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([CsvExportService], (service: CsvExportService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
119
client/src/app/core/services/csv-export.service.ts
Normal file
119
client/src/app/core/services/csv-export.service.ts
Normal file
@ -0,0 +1,119 @@
|
||||
import { BaseViewModel } from '../../site/base/base-view-model';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { FileExportService } from './file-export.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class CsvExportService {
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public constructor(protected exporter: FileExportService, private translate: TranslateService) {}
|
||||
|
||||
/**
|
||||
* Saves an array of model data to a CSV.
|
||||
* @param data Array of Model instances to be saved
|
||||
* @param columns Column definitions
|
||||
* @param filename name of the resulting file
|
||||
* @param options optional:
|
||||
* lineSeparator (defaults to \r\n windows style line separator),
|
||||
* columnseparator defaults to semicolon (other usual separators are ',' '\T' (tab), ' 'whitespace)
|
||||
*/
|
||||
public export<T extends BaseViewModel>(
|
||||
data: T[],
|
||||
columns: {
|
||||
property: keyof T; // name of the property used for export
|
||||
label?: string;
|
||||
assemble?: string; // (if property is further child object, the property of these to be used)
|
||||
}[],
|
||||
filename: string,
|
||||
{ lineSeparator = '\r\n', columnSeparator = ';' }: { lineSeparator?: string; columnSeparator?: string } = {}
|
||||
): void {
|
||||
const allLines = []; // Array of arrays of entries
|
||||
const usedColumns = []; // mapped properties to be included
|
||||
|
||||
// initial array of usable text separators. The first character not used
|
||||
// in any text data or as column separator will be used as text separator
|
||||
let tsList = ['"', "'", '`', '/', '\\', ';', '.'];
|
||||
|
||||
if (lineSeparator === columnSeparator) {
|
||||
throw new Error('lineseparator and columnseparator must differ from each other');
|
||||
}
|
||||
|
||||
tsList = this.checkCsvTextSafety(lineSeparator, tsList);
|
||||
tsList = this.checkCsvTextSafety(columnSeparator, tsList);
|
||||
|
||||
// create header data
|
||||
const header = [];
|
||||
columns.forEach(column => {
|
||||
const rawLabel: string = column.label ? column.label : (column.property as string);
|
||||
const colLabel = this.capitalizeTranslate(rawLabel);
|
||||
tsList = this.checkCsvTextSafety(colLabel, tsList);
|
||||
header.push(colLabel);
|
||||
usedColumns.push(column.property);
|
||||
});
|
||||
allLines.push(header);
|
||||
// create lines
|
||||
data.forEach(item => {
|
||||
const line = [];
|
||||
for (let i = 0; i < usedColumns.length; i++ ){
|
||||
const property = usedColumns[i];
|
||||
let prop: any = item[property];
|
||||
if (columns[i].assemble){
|
||||
prop = item[property].map(subitem => this.translate.instant(subitem[columns[i].assemble])).join(',');
|
||||
}
|
||||
tsList = this.checkCsvTextSafety(prop, tsList);
|
||||
line.push(prop);
|
||||
};
|
||||
allLines.push(line);
|
||||
});
|
||||
|
||||
// assemble lines, putting text separator in place
|
||||
if (!tsList.length) {
|
||||
throw new Error('no usable text separator left for valid csv text');
|
||||
}
|
||||
|
||||
const allLinesAssembled = [];
|
||||
allLines.forEach(line => {
|
||||
const assembledLine = [];
|
||||
line.forEach(item => {
|
||||
if (typeof item === 'number') {
|
||||
assembledLine.push(item.toString(10));
|
||||
} else if (item === null || item === undefined || item === '') {
|
||||
assembledLine.push('');
|
||||
} else if (item === true) {
|
||||
assembledLine.push('1');
|
||||
} else if (item === false) {
|
||||
assembledLine.push('0');
|
||||
} else {
|
||||
assembledLine.push(tsList[0] + item + tsList[0]);
|
||||
}
|
||||
});
|
||||
allLinesAssembled.push(assembledLine.join(columnSeparator));
|
||||
});
|
||||
this.exporter.saveFile(allLinesAssembled.join(lineSeparator), filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given input contains any of the characters defined in a list
|
||||
* used for textseparators. The list is then returned without the 'special'
|
||||
* characters, as they may not be used as text separator in this csv.
|
||||
* @param input any input to be sent to CSV
|
||||
* @param tsList The list of special characters to check.
|
||||
*/
|
||||
public checkCsvTextSafety(input: any, tsList: string[]): string[] {
|
||||
if (input === null || input === undefined ) {
|
||||
return tsList;
|
||||
}
|
||||
|
||||
const inputAsString = String(input);
|
||||
return tsList.filter(char => inputAsString.indexOf(char) < 0);
|
||||
}
|
||||
|
||||
private capitalizeTranslate(input: string): string {
|
||||
const temp = input.charAt(0).toUpperCase() + input.slice(1);
|
||||
return this.translate.instant(temp);
|
||||
}
|
||||
}
|
17
client/src/app/core/services/file-export.service.spec.ts
Normal file
17
client/src/app/core/services/file-export.service.spec.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { FileExportService } from './file-export.service';
|
||||
import { E2EImportsModule } from '../../../e2e-imports.module';
|
||||
|
||||
describe('FileExportService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule],
|
||||
providers: [FileExportService]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([FileExportService], (service: FileExportService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
33
client/src/app/core/services/file-export.service.ts
Normal file
33
client/src/app/core/services/file-export.service.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { saveAs } from 'file-saver';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class FileExportService {
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public constructor() {}
|
||||
|
||||
/**
|
||||
* Saves a file
|
||||
* @param file
|
||||
* @param filename
|
||||
*/
|
||||
public saveFile(file: BlobPart, filename: string): void {
|
||||
const blob = new Blob([file]);
|
||||
saveAs(blob, filename, { autoBOM: true });
|
||||
// autoBOM = automatic byte-order-mark
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a file name for characters that might break.
|
||||
* @param filename A name used to save a file
|
||||
*/
|
||||
protected validateFileName(filename: string): boolean {
|
||||
// everything not containing \/?%*:|"<> and not ending on a dot
|
||||
const pattern = new RegExp(/^[^\\\/\?%\*:\|\"\<\>]*[^\.]+$/i);
|
||||
return pattern.test(filename);
|
||||
}
|
||||
}
|
@ -75,8 +75,8 @@
|
||||
<span translate>Import ...</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item>
|
||||
<button mat-menu-item (click)="csvExportUserList()">
|
||||
<mat-icon>archive</mat-icon>
|
||||
<span translate>Export ...</span>
|
||||
<span translate>Export as csv</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CsvExportService } from '../../../../core/services/csv-export.service';
|
||||
|
||||
import { ViewUser } from '../../models/view-user';
|
||||
import { UserRepositoryService } from '../../services/user-repository.service';
|
||||
@ -17,6 +18,7 @@ import { Router, ActivatedRoute } from '@angular/router';
|
||||
styleUrls: ['./user-list.component.scss']
|
||||
})
|
||||
export class UserListComponent extends ListViewBaseComponent<ViewUser> implements OnInit {
|
||||
|
||||
/**
|
||||
* The usual constructor for components
|
||||
* @param repo the user repository
|
||||
@ -28,7 +30,8 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
protected titleService: Title,
|
||||
protected translate: TranslateService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute
|
||||
private route: ActivatedRoute,
|
||||
protected csvExport: CsvExportService
|
||||
) {
|
||||
super(titleService, translate);
|
||||
}
|
||||
@ -69,4 +72,26 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
public onPlusButton(): void {
|
||||
this.router.navigate(['./new'], { relativeTo: this.route });
|
||||
}
|
||||
|
||||
// TODO save all data from the dataSource
|
||||
public csvExportUserList(): void {
|
||||
this.csvExport.export(
|
||||
this.dataSource.data,
|
||||
[
|
||||
{ property: 'title' },
|
||||
{ property: 'first_name', label: 'First Name' },
|
||||
{ property: 'last_name', label: 'Last Name' },
|
||||
{ property: 'structure_level', label: 'Structure Level' },
|
||||
{ property: 'participant_number', label: 'Participant Number' },
|
||||
{ property: 'groups', assemble: 'name'},
|
||||
{ property: 'comment' },
|
||||
{ property: 'is_active', label: 'Active' },
|
||||
{ property: 'is_present', label: 'Presence' },
|
||||
{ property: 'is_committee', label: 'Committee' },
|
||||
{ property: 'default_password', label: 'Default password' },
|
||||
{ property: 'email', label: 'E-Mail' }
|
||||
],
|
||||
'export.csv'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user