Fully generic CRUD pages

This commit is contained in:
2023-01-18 19:43:13 +01:00
parent 15f101eb9f
commit ddb3706e44
9 changed files with 84 additions and 62 deletions

View File

@@ -1,7 +1,7 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; import { Routes, RouterModule } from '@angular/router';
import {EntitiesComponent, EntityCardComponent, EntityListComponent, EntityNewComponent} from "./entities.component"; import { EntityCardComponent, EntityListComponent, EntityNewComponent } from "./entities.component";
import {ListComponent} from "../../../common/crud/list/list.component"; import {ListComponent} from "../../../common/crud/list/list.component";
import {CardComponent} from "../../../common/crud/card/card.component"; import {CardComponent} from "../../../common/crud/card/card.component";
import {ColorsComponent} from "../theme/colors.component"; import {ColorsComponent} from "../theme/colors.component";

View File

@@ -6,35 +6,32 @@ import { DOCUMENT } from '@angular/common';
import { getStyle, rgbToHex } from '@coreui/utils/src'; import { getStyle, rgbToHex } from '@coreui/utils/src';
@Component({
template: ''
})
export class EntitiesComponent {
constructor() { export class BaseEntitiesComponent {
} protected resource: string = "Entity";
} }
@Component({ @Component({
template: '<crud-list></crud-list>' template: '<crud-list [resource]="this.resource"></crud-list>'
}) })
export class EntityListComponent { export class EntityListComponent extends BaseEntitiesComponent{
} }
@Component({ @Component({
template: '<crud-card></crud-card>' template: '<crud-card [resource]="this.resource"></crud-card>'
}) })
export class EntityNewComponent { export class EntityNewComponent extends BaseEntitiesComponent {
} }
@Component({ @Component({
template: '<crud-card [resource_id]="this.resource_id"></crud-card>' template: '<crud-card [resource]="this.resource" [resource_id]="this.resource_id"></crud-card>'
}) })
export class EntityCardComponent implements OnInit { export class EntityCardComponent extends BaseEntitiesComponent implements OnInit {
resource_id: string | null = null; resource_id: string | null = null;
constructor(private route: ActivatedRoute,) { constructor(private route: ActivatedRoute,) {
super();
} }
ngOnInit(): void { ngOnInit(): void {

View File

@@ -3,7 +3,7 @@ import { NgModule } from '@angular/core';
import { EntitiesRoutingModule } from './entities-routing.module'; import { EntitiesRoutingModule } from './entities-routing.module';
import { CrudModule } from '@common/crud/crud.module' import { CrudModule } from '@common/crud/crud.module'
import {EntitiesComponent, EntityCardComponent, EntityListComponent, EntityNewComponent} from "./entities.component"; import { EntityCardComponent, EntityListComponent, EntityNewComponent} from "./entities.component";
@@ -14,7 +14,6 @@ import {EntitiesComponent, EntityCardComponent, EntityListComponent, EntityNewCo
EntitiesRoutingModule EntitiesRoutingModule
], ],
declarations: [ declarations: [
EntitiesComponent,
EntityListComponent, EntityListComponent,
EntityNewComponent, EntityNewComponent,
EntityCardComponent, EntityCardComponent,

View File

@@ -18,11 +18,11 @@ export interface Model {
styleUrls: ['./card.component.css'] styleUrls: ['./card.component.css']
}) })
export class CardComponent implements OnInit { export class CardComponent implements OnInit {
@Input() resource: string | undefined;
@Input() resource_id: string | null = null; @Input() resource_id: string | null = null;
form = new FormGroup({}); form = new FormGroup({});
model = {}; model = {};
fields: FormlyFieldConfig[] = []; fields: FormlyFieldConfig[] = [];
resource: string = "Entity";
schemas = JSON.parse(`{}`); schemas = JSON.parse(`{}`);
@@ -41,14 +41,14 @@ export class CardComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this._loading$.next(true); this._loading$.next(true);
if (this.isCreateForm()) { if (this.isCreateForm()) {
this.jsonSchemasService.getCreateResource(this.resource).subscribe((schemas: any) => { this.jsonSchemasService.getCreateResource(this.resource!).subscribe((schemas: any) => {
this.fields = [this.formlyJsonschema.toFieldConfig(schemas)]; this.fields = [this.formlyJsonschema.toFieldConfig(schemas)];
}) })
} else { } else {
this.jsonSchemasService.getUpdateResource(this.resource).subscribe((schemas: any) => { this.jsonSchemasService.getUpdateResource(this.resource!).subscribe((schemas: any) => {
this.fields = [this.formlyJsonschema.toFieldConfig(schemas)]; this.fields = [this.formlyJsonschema.toFieldConfig(schemas)];
}) })
this.crudService.get(this.resource_id!).subscribe((model: any) => { this.crudService.get(this.resource!, this.resource_id!).subscribe((model: any) => {
this.model = model this.model = model
this._loading$.next(false); this._loading$.next(false);
}); });
@@ -58,12 +58,12 @@ export class CardComponent implements OnInit {
onSubmit(model: any) { onSubmit(model: any) {
this._loading$.next(true); this._loading$.next(true);
if (this.isCreateForm()) { if (this.isCreateForm()) {
this.crudService.create(model).subscribe((response: any) => { this.crudService.create(this.resource!, model).subscribe((response: any) => {
this._loading$.next(false); this._loading$.next(false);
this.router.navigateByUrl('/entities/' + response.id); this.router.navigateByUrl(response.id);
}); });
} else { } else {
this.crudService.update(model).subscribe((model: any) => { this.crudService.update(this.resource!, model).subscribe((model: any) => {
this.model = model; this.model = model;
this._loading$.next(false); this._loading$.next(false);
}); });

View File

@@ -12,7 +12,7 @@ import { FormlyBootstrapModule } from '@ngx-formly/bootstrap';
import { CardComponent } from './card/card.component'; import { CardComponent } from './card/card.component';
import { ListComponent } from './list/list.component'; import { ListComponent } from './list/list.component';
import { ObjectTypeComponent } from "./object.type"; import { ObjectTypeComponent } from "./object.type";
import { CrudService } from "./crud.service"; import {ApiService, CrudService} from "./crud.service";
import {NgbModule} from "@ng-bootstrap/ng-bootstrap"; import {NgbModule} from "@ng-bootstrap/ng-bootstrap";
import {JsonschemasService} from "./jsonschemas.service"; import {JsonschemasService} from "./jsonschemas.service";
@@ -23,7 +23,7 @@ import {JsonschemasService} from "./jsonschemas.service";
ListComponent, ListComponent,
ObjectTypeComponent ObjectTypeComponent
], ],
providers: [ CrudService, JsonschemasService ], providers: [ JsonschemasService, ApiService, CrudService ],
imports: [ imports: [
CommonModule, CommonModule,
HttpClientModule, HttpClientModule,

View File

@@ -1,41 +1,47 @@
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable, Inject } from '@angular/core';
import { Schema } from "./jsonschemas.service"; import { Schema } from "./jsonschemas.service";
@Injectable() @Injectable()
export class CrudService { export class ApiService {
constructor(protected http: HttpClient) {}
constructor(private http: HttpClient) {} protected api_root: string = '/api/v1'
public getSchema() {
return this.http.get<Schema>(`${this.api_root}/openapi.json`);
}
}
@Injectable()
export class CrudService extends ApiService {
public loading: boolean = false; public loading: boolean = false;
public getSchema() { public getList(resource: string, page: number, size: number, sortColumn: string, sortDirection: string) {
return this.http.get<Schema>(`/api/v1/openapi.json`); return this.http.get<{ items: [{}] }>(
} `${this.api_root}/${resource.toLowerCase()}/?size=${size}&page=${page + 1}&sort_by=${sortDirection}(${sortColumn})`
public getList(page: number, size: number, sortColumn: string, sortDirection: string) {
return this.http.get<{ menu: [{}] }>(
`/api/v1/entity/?size=${size}&page=${page + 1}&sort_by=${sortDirection}(${sortColumn})`
); );
} }
public get(id: string) { public get(resource: string, id: string) {
return this.http.get<{}>( return this.http.get<{}>(
`/api/v1/entity/${id}` `${this.api_root}/${resource.toLowerCase()}/${id}`
); );
} }
public update(model: any) { public update(resource: string, model: any) {
return this.http.put<{ menu: [{}] }>( return this.http.put<{ menu: [{}] }>(
`/api/v1/entity/${model._id}`, `${this.api_root}/${resource.toLowerCase()}/${model._id}`,
model model
); );
} }
public create(model: any) { public create(resource: string, model: any) {
return this.http.post<{ menu: [{}] }>( return this.http.post<{ menu: [{}] }>(
`/api/v1/entity/`, `${this.api_root}/${resource.toLowerCase()}/`,
model model
); );
} }

View File

@@ -1,4 +1,4 @@
import {CrudService} from "./crud.service"; import {ApiService} from "./crud.service";
import {Observable} from "rxjs"; import {Observable} from "rxjs";
import {Injectable} from "@angular/core"; import {Injectable} from "@angular/core";
@@ -9,12 +9,12 @@ import {Injectable} from "@angular/core";
export class JsonschemasService { export class JsonschemasService {
private rawSchemas: any | null = null; private rawSchemas: any | null = null;
constructor(private crudService: CrudService) {} constructor(private apiService: ApiService) {}
getSchemas(): Observable<Schema> { getSchemas(): Observable<Schema> {
return new Observable<Schema>((observer) => { return new Observable<Schema>((observer) => {
if (this.rawSchemas === null) { if (this.rawSchemas === null) {
this.crudService.getSchema().subscribe((jsonSchemas: any) => { this.apiService.getSchema().subscribe((jsonSchemas: any) => {
this.rawSchemas = jsonSchemas; this.rawSchemas = jsonSchemas;
observer.next(this.rawSchemas) observer.next(this.rawSchemas)
}); });

View File

@@ -16,18 +16,14 @@
<table class="table table-striped"> <table class="table table-striped">
<thead> <thead>
<tr> <tr>
<th scope="col">#</th> <th *ngFor="let col of this.displayedColumns" scope="col" sortable="name" (sort)="onSort($event)">{{ col }}</th>
<th scope="col" sortable="name" (sort)="onSort($event)">Name</th>
<th scope="col" sortable="type" (sort)="onSort($event)">Type</th>
<th scope="col" sortable="address" (sort)="onSort($event)">Address</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let row of listData$ | async" (click)="onSelect(row._id)"> <tr *ngFor="let row of listData$ | async" (click)="onSelect(row._id)">
<th scope="row">{{ row.id }}</th> <td *ngFor="let col of this.displayedColumns">
<td><ngb-highlight [result]="row.name" [term]="searchTerm"></ngb-highlight></td> <ngb-highlight [result]="row[col]" [term]="searchTerm"></ngb-highlight>
<td><ngb-highlight [result]="row.type" [term]="searchTerm"></ngb-highlight></td> </td>
<td><ngb-highlight [result]="row.address" [term]="searchTerm"></ngb-highlight></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View File

@@ -1,9 +1,9 @@
import { Component, ViewChildren, QueryList } from '@angular/core'; import { Component, ViewChildren, QueryList, Input, OnInit } from '@angular/core';
import { BehaviorSubject } from "rxjs"; import { BehaviorSubject } from "rxjs";
import { Location } from '@angular/common'; import { ActivatedRoute, Router } from '@angular/router';
import { Router } from '@angular/router'; import { CrudService } from "../crud.service";
import { CrudService} from "../crud.service";
import { NgbdSortableHeader, SortColumn, SortDirection } from './sortable.directive'; import { NgbdSortableHeader, SortColumn, SortDirection } from './sortable.directive';
import { JsonschemasService } from "../jsonschemas.service";
interface SearchResult { interface SearchResult {
@@ -22,18 +22,42 @@ interface State {
@Component({ @Component({
selector: 'crud-list', selector: 'crud-list',
templateUrl: './list.component.html', templateUrl: './list.component.html',
styleUrls: ['./list.component.css'] styleUrls: ['./list.component.css'],
}) })
export class ListComponent { export class ListComponent implements OnInit {
displayedColumns: string[] = ['type', 'name', 'address', '_id',]; @Input() resource: string = "";
@Input() columns: string[] = [];
public displayedColumns: string[] = [];
loading: boolean = false; loading: boolean = false;
@ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader> = new QueryList<NgbdSortableHeader>(); @ViewChildren(NgbdSortableHeader) headers: QueryList<NgbdSortableHeader> = new QueryList<NgbdSortableHeader>();
constructor(private location: Location, public service: CrudService, private router: Router) { constructor(private service: CrudService,
private jsonSchemasService: JsonschemasService,
private router: Router,
private route: ActivatedRoute,
) { }
ngOnInit(): void {
this.jsonSchemasService.getUpdateResource(this.resource!).subscribe((schemas: any) => {
if (this.columns.length == 0) {
for (let param_name in schemas.properties) {
if (param_name != "_id") {
this.displayedColumns.push(param_name);
}
}
} else {
for (let column in this.columns) {
if (column in schemas.properties) {
this.displayedColumns.push(column);
}
}
}
})
this._search(); this._search();
} }
private _loading$ = new BehaviorSubject<boolean>(true); private _loading$ = new BehaviorSubject<boolean>(true);
//private _search$ = new Subject<void>(); //private _search$ = new Subject<void>();
@@ -50,7 +74,7 @@ export class ListComponent {
private _search() { private _search() {
this._loading$.next(true); this._loading$.next(true);
this.service.getList(this.page - 1, this.pageSize, this.sortColumn, this.sortDirection).subscribe((data: any) => { this.service.getList(this.resource, this.page - 1, this.pageSize, this.sortColumn, this.sortDirection).subscribe((data: any) => {
this._listData$.next(data.items); this._listData$.next(data.items);
this._total$.next(data.total); this._total$.next(data.total);
this._state.pageSize = data.size; this._state.pageSize = data.size;
@@ -72,7 +96,7 @@ export class ListComponent {
} }
onSelect(id: string) { onSelect(id: string) {
this.router.navigateByUrl('/entities/' + id); this.router.navigate([id], {relativeTo: this.route});
} }
get listData$() { get listData$() {