Last active
August 30, 2022 08:29
-
-
Save petyosi/088a41fddbd981b62a90b8e56b0432e9 to your computer and use it in GitHub Desktop.
crud-app-final
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {BrowserModule} from '@angular/platform-browser'; | |
import {NgModule} from '@angular/core'; | |
import {HttpModule} from '@angular/http'; | |
import {FormsModule} from '@angular/forms'; | |
import {AgGridModule} from 'ag-grid-angular'; | |
import {AthleteService} from './services/athlete.service'; | |
import {StaticDataService} from './services/static-data.service'; | |
import {AppComponent} from './app.component'; | |
import {GridComponent} from './grid/grid.component'; | |
import {AthleteEditScreenComponent} from './athlete-edit-screen/athlete-edit-screen.component'; | |
@NgModule({ | |
declarations: [ | |
AppComponent, | |
GridComponent, | |
AthleteEditScreenComponent | |
], | |
imports: [ | |
BrowserModule, | |
HttpModule, | |
FormsModule, | |
AgGridModule.withComponents([]) | |
], | |
providers: [ | |
AthleteService, | |
StaticDataService | |
], | |
bootstrap: [AppComponent] | |
}) | |
export class AppModule { | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.input-panel { | |
position: absolute; | |
border-radius: 5px; | |
border: solid lightgray 1px; | |
padding: 15px; | |
background-color: whitesmoke; | |
} | |
.submit-pane { | |
overflow: hidden; | |
margin-top: 10px; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="input-panel" [style.width]="width" [style.top]="top" [style.left]="left" #panel> | |
<div style="display: inline-block"> | |
<div style="float: left"> | |
Name: <input [(ngModel)]="name"/> | |
</div> | |
<div style="float: left; padding-left: 10px"> | |
Country: | |
<select [(ngModel)]="country" [compareWith]="countryComparator"> | |
<option disabled selected>Country...</option> | |
<option *ngFor="let country of countries" [ngValue]="country">{{ country.name }}</option> | |
</select> | |
</div> | |
</div> | |
<div> | |
<button (click)="insertNewResult()" class="action-button">Insert New Result</button> | |
<ag-grid-angular style="width: 100%; height: 200px;" | |
class="ag-theme-balham" | |
[columnDefs]="columnDefs" | |
[rowData]="rowData" | |
(gridReady)="onGridReady($event)" | |
(rowValueChanged)="onRowValueChanged($event)"> | |
</ag-grid-angular> | |
</div> | |
<div class="submit-pane"> | |
<button (click)="saveAthlete()" [disabled]="!isValidAthlete()" class="action-button" style="float: right">Save | |
Athlete | |
</button> | |
</div> | |
</div> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core'; | |
import {ColDef, ColumnApi, GridApi} from 'ag-grid'; | |
import {StaticDataService} from '../services/static-data.service'; | |
import {Result} from '../model/result.model'; | |
import {Sport} from '../model/sport.model'; | |
import {Country} from '../model/country.model'; | |
import {Athlete} from '../model/athlete.model'; | |
@Component({ | |
selector: 'app-athlete-edit-screen', | |
templateUrl: './athlete-edit-screen.component.html', | |
styleUrls: ['./athlete-edit-screen.component.css'] | |
}) | |
export class AthleteEditScreenComponent implements OnInit { | |
// gridApi and columnApi | |
private api: GridApi; | |
private columnApi: ColumnApi; | |
// static data | |
private sports: Sport[]; | |
private countries: Country[]; | |
// interim/form data | |
private name: string; | |
private country: Country; | |
private rowData: Result[] = []; | |
// the results sub-table columns | |
private columnDefs: ColDef[]; | |
@Input() containerCoords: any = null; | |
@Input() athlete: Athlete = null; | |
@Output() onAthleteSaved = new EventEmitter<Athlete>(); | |
// to position this component relative to the containing component | |
@ViewChild('panel', {read: ElementRef}) public panel; | |
private width: any; | |
private left: any; | |
private top: any; | |
constructor(staticDataService: StaticDataService) { | |
staticDataService.countries().subscribe( | |
countries => this.countries = countries.sort(StaticDataService.alphabeticalSort()), | |
error => console.log(error) | |
); | |
staticDataService.sports().subscribe( | |
sports => { | |
// store reference to sports, after sorting alphabetically | |
this.sports = sports.sort(StaticDataService.alphabeticalSort()); | |
// create the column defs | |
this.columnDefs = this.createColumnDefs(this.sports) | |
}, | |
error => console.log(error) | |
); | |
} | |
ngOnInit() { | |
this.setPanelCoordinates(); | |
if (this.athlete) { | |
this.name = this.athlete.name; | |
this.country = this.athlete.country; | |
this.rowData = this.athlete.results.slice(0); | |
} | |
} | |
countryComparator(c1: Country, c2: Country): boolean { | |
return c1 && c2 ? c1.id === c2.id : false; | |
} | |
insertNewResult() { | |
// insert a blank new row, providing the first sport as a default in the sport column | |
const updates = this.api.updateRowData( | |
{ | |
add: [{ | |
sport: this.sports[0] | |
}] | |
} | |
); | |
this.api.startEditingCell({ | |
rowIndex: updates.add[0].rowIndex, | |
colKey: 'age' | |
}); | |
} | |
isValidAthlete() { | |
return this.name && this.name !== '' && | |
this.country; | |
} | |
saveAthlete() { | |
const athlete = new Athlete(); | |
athlete.id = this.athlete ? this.athlete.id : null; | |
athlete.version = this.athlete ? this.athlete.version : undefined; | |
athlete.name = this.name; | |
athlete.country = this.country; | |
athlete.results = []; | |
this.api.forEachNode((node) => { | |
const {data} = node; | |
athlete.results.push(<Result> { | |
id: data.id, | |
version: data.version, | |
age: data.age, | |
year: data.year, | |
date: data.date, | |
bronze: data.bronze, | |
silver: data.silver, | |
gold: data.gold, | |
sport: data.sport | |
}); | |
}); | |
this.onAthleteSaved.emit(athlete); | |
} | |
onGridReady(params): void { | |
this.api = params.api; | |
this.columnApi = params.columnApi; | |
this.api.sizeColumnsToFit(); | |
// temp fix until AG-1181 is fixed | |
this.api.hideOverlay(); | |
} | |
// create some simple column definitions | |
private createColumnDefs(sports: Sport[]) { | |
return [ | |
{ | |
field: 'age', | |
editable: true | |
}, | |
{ | |
field: 'year', | |
editable: true | |
}, | |
{ | |
field: 'date', | |
editable: true | |
}, | |
{ | |
field: 'bronze', | |
editable: true | |
}, | |
{ | |
field: 'silver', | |
editable: true | |
}, | |
{ | |
field: 'gold', | |
editable: true | |
}, | |
{ | |
field: 'sport', | |
cellRenderer: (params) => params.data.sport.name, | |
editable: true, | |
cellEditor: 'richSelect', | |
cellEditorParams: { | |
values: sports, | |
cellRenderer: (params) => params.value.name | |
} | |
} | |
] | |
} | |
private setPanelCoordinates() { | |
// make our width 100pixels smaller than the container | |
this.width = (this.containerCoords.width - 100); | |
// set our left position to be the container left position plus half the difference in widths between this | |
// component and the container, minus the 15px padding | |
this.left = Math.floor(this.containerCoords.left + (this.containerCoords.width - this.width) / 2 - 15) + 'px'; | |
// set our left position to be the container top position plus half the difference in height between this | |
// component and the container | |
this.top = Math.floor(this.containerCoords.top + (this.containerCoords.height - this.panel.nativeElement.offsetHeight) / 2) + 'px'; | |
// add the px suffix back in (omitted above so that maths can work) | |
this.width = this.width + 'px' | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Country} from './country.model'; | |
import {Result} from './result.model'; | |
export class Athlete { | |
id: number; | |
version: number; | |
name: string; | |
country: Country; | |
results: Result[]; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Injectable} from '@angular/core'; | |
import {Athlete} from '../model/athlete.model'; | |
import {Headers, Http, RequestOptions, Response} from '@angular/http'; | |
import 'rxjs/add/operator/map' | |
import 'rxjs/add/operator/catch'; | |
import {Observable} from 'rxjs/Observable'; | |
@Injectable() | |
export class AthleteService { | |
static REQUEST_OPTIONS: RequestOptions = new RequestOptions({headers: new Headers({'Content-Type': 'application/json'})}); | |
private apiRootUrl = 'http://localhost:8080'; | |
private findAllUrl = this.apiRootUrl + '/athletes'; | |
private findByIdUrl = this.apiRootUrl + '/athlete'; | |
private saveUpdateUrl = this.apiRootUrl + '/saveAthlete'; | |
private deleteUrl = this.apiRootUrl + '/deleteAthlete'; | |
constructor(private http: Http) { | |
} | |
findAll(): Observable<Athlete[]> { | |
return this.http.get(this.findAllUrl) | |
.map((response: Response) => response.json()) | |
.catch(this.defaultErrorHandler()); | |
} | |
findById(id: number): Observable<Athlete> { | |
return this.http.get(this.findByIdUrl + '/' + id) | |
.map((response: Response) => response.json()) | |
.catch(this.defaultErrorHandler()); | |
} | |
save(athlete: Athlete): Observable<Athlete> { | |
return this.http.post(this.saveUpdateUrl, athlete, AthleteService.REQUEST_OPTIONS) | |
.map((response: Response) => response.json()) | |
.catch(this.defaultErrorHandler()); | |
} | |
delete(athlete: Athlete): Observable<boolean> { | |
return this.http.post(this.deleteUrl, athlete.id, AthleteService.REQUEST_OPTIONS) | |
.map((response: Response) => response.json()) | |
.catch(this.defaultErrorHandler()); | |
} | |
private defaultErrorHandler() { | |
return (error: any) => { | |
console.log(error); | |
return Observable.throw(error.json().error || 'Server error') | |
}; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {StaticData} from './static-data.model'; | |
export class Country implements StaticData { | |
id: number; | |
name: string; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div> | |
<button (click)="insertNewRow()" [disabled]="editInProgress" class="action-button">Insert New Row</button> | |
<button (click)="deleteSelectedRows()" [disabled]="!rowsSelected() || editInProgress" class="action-button">Delete | |
Selected Row | |
</button> | |
</div> | |
<div> | |
<ag-grid-angular style="width: 100%; height: 500px;" | |
class="ag-theme-balham" | |
#grid | |
[columnDefs]="columnDefs" | |
[rowData]="rowData" | |
rowSelection="multiple" | |
suppressRowClickSelection | |
suppressHorizontalScroll | |
suppressClickEdit | |
[getRowNodeId]="getRowNodeId" | |
(gridReady)="onGridReady($event)" | |
(rowDoubleClicked)="onRowDoubleClicked($event)"> | |
</ag-grid-angular> | |
</div> | |
<ng-template [ngIf]="editInProgress"> | |
<app-athlete-edit-screen [athlete]="athleteBeingEdited" | |
[containerCoords]="containerCoords" | |
(onAthleteSaved)="onAthleteSaved($event)"></app-athlete-edit-screen> | |
</ng-template> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Component, ElementRef, OnInit, ViewChild} from '@angular/core'; | |
import {Observable} from 'rxjs/Observable'; | |
import 'rxjs/add/observable/forkJoin'; | |
import {ColDef, ColumnApi, GridApi} from 'ag-grid'; | |
import {AthleteService} from '../services/athlete.service'; | |
import {Athlete} from '../model/athlete.model'; | |
import {StaticDataService} from '../services/static-data.service'; | |
import {Country} from '../model/country.model'; | |
// we need to import this as we're making use of enterprise features, such as the richSelect cell editor | |
import 'ag-grid-enterprise'; | |
@Component({ | |
selector: 'app-grid', | |
templateUrl: './grid.component.html', | |
styleUrls: ['./grid.component.css'] | |
}) | |
export class GridComponent { | |
// row data and column definitions | |
private rowData: Athlete[]; | |
private columnDefs: ColDef[]; | |
// gridApi and columnApi | |
private api: GridApi; | |
private columnApi: ColumnApi; | |
private editInProgress: boolean = false; | |
private athleteBeingEdited: Athlete = null; | |
private containerCoords: {} = null; | |
@ViewChild('grid', {read: ElementRef}) public grid; | |
// inject the athleteService | |
constructor(private athleteService: AthleteService, | |
staticDataService: StaticDataService) { | |
staticDataService.countries().subscribe( | |
countries => this.columnDefs = this.createColumnDefs(countries), | |
error => console.log(error) | |
); | |
this.athleteService.findAll().subscribe( | |
athletes => this.rowData = athletes, | |
error => console.log(error) | |
) | |
} | |
getRowNodeId(params) { | |
return params.id; | |
} | |
onAthleteSaved(athleteToSave: Athlete) { | |
this.athleteService.save(athleteToSave) | |
.subscribe( | |
savedAthlete => { | |
console.log('Athlete saved', savedAthlete.name); | |
const added = []; | |
const updated = []; | |
if (athleteToSave.id) { | |
updated.push(savedAthlete); | |
} else { | |
added.push(savedAthlete); | |
} | |
this.api.updateRowData( | |
{ | |
add: added, | |
update: updated | |
} | |
); | |
}, | |
error => console.log(error) | |
); | |
this.athleteBeingEdited = null; | |
this.editInProgress = false; | |
} | |
// one grid initialisation, grab the APIs and auto resize the columns to fit the available space | |
onGridReady(params): void { | |
this.api = params.api; | |
this.columnApi = params.columnApi; | |
this.api.sizeColumnsToFit(); | |
} | |
// create some simple column definitions | |
private createColumnDefs(countries: Country[]) { | |
return [ | |
{ | |
field: 'name', | |
editable: true, | |
checkboxSelection: true | |
}, | |
{ | |
field: 'country', | |
cellRenderer: (params) => params.data.country.name, | |
editable: true, | |
cellEditor: 'richSelect', | |
cellEditorParams: { | |
values: countries, | |
cellRenderer: (params) => params.value.name | |
} | |
}, | |
{ | |
field: 'results', | |
valueGetter: (params) => params.data.results.length | |
} | |
] | |
} | |
onRowDoubleClicked(params: any) { | |
if (this.editInProgress) { | |
return; | |
} | |
this.updateContainerCoords(); | |
this.athleteBeingEdited = <Athlete>params.data; | |
this.editInProgress = true; | |
} | |
insertNewRow() { | |
this.updateContainerCoords(); | |
this.editInProgress = true; | |
} | |
private updateContainerCoords() { | |
this.containerCoords = { | |
top: this.grid.nativeElement.offsetTop, | |
left: this.grid.nativeElement.offsetLeft, | |
height: this.grid.nativeElement.offsetHeight, | |
width: this.grid.nativeElement.offsetWidth | |
}; | |
} | |
rowsSelected() { | |
return this.api && this.api.getSelectedRows().length > 0; | |
} | |
deleteSelectedRows() { | |
const selectRows = this.api.getSelectedRows(); | |
// create an Observable for each row to delete | |
const deleteSubscriptions = selectRows.map((rowToDelete) => { | |
return this.athleteService.delete(rowToDelete); | |
}); | |
// then subscribe to these and once all done, update the grid | |
Observable.forkJoin(...deleteSubscriptions).subscribe( | |
results => { | |
// only redraw removed rows... | |
this.api.updateRowData( | |
{ | |
remove: selectRows | |
} | |
); | |
} | |
); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Sport} from './sport.model'; | |
export class Result { | |
id: number; | |
version: number; | |
age: number; | |
year: number; | |
date: string; | |
gold: number; | |
silver: number; | |
bronze: number; | |
sport: Sport; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {StaticData} from './static-data.model'; | |
export class Sport implements StaticData { | |
id: number; | |
name: string; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export interface StaticData { | |
name: string; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import {Injectable} from '@angular/core'; | |
import {Http, Response} from '@angular/http'; | |
import 'rxjs/add/operator/map' | |
import 'rxjs/add/operator/catch'; | |
import {Observable} from 'rxjs/Observable'; | |
import {Sport} from '../model/sport.model'; | |
import {Country} from '../model/country.model'; | |
import {StaticData} from '../model/static-data.model'; | |
@Injectable() | |
export class StaticDataService { | |
private apiRootUrl = 'http://localhost:8080'; | |
private countriesUrl = this.apiRootUrl + '/countries'; | |
private sportsUrl = this.apiRootUrl + '/sports'; | |
static alphabeticalSort() { | |
return (a: StaticData, b: StaticData) => a.name.localeCompare(b.name); | |
} | |
constructor(private http: Http) { | |
} | |
countries(): Observable<Country[]> { | |
return this.http.get(this.countriesUrl) | |
.map((response: Response) => response.json()) | |
.catch(this.defaultErrorHandler()); | |
} | |
sports(): Observable<Sport[]> { | |
return this.http.get(this.sportsUrl) | |
.map((response: Response) => response.json()) | |
.catch(this.defaultErrorHandler()); | |
} | |
private defaultErrorHandler() { | |
return (error: any) => Observable.throw(error.json().error || 'Server error'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment