US05: Make it possible for the user to search for a horse using the UI
This commit is contained in:
parent
129b3c534b
commit
a3f6276fc4
@ -2,6 +2,7 @@ import { NgModule } from '@angular/core';
|
|||||||
import { Routes, RouterModule } from '@angular/router';
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
import { OwnerComponent } from './component/owner/owner.component';
|
import { OwnerComponent } from './component/owner/owner.component';
|
||||||
import { HorseComponent } from './component/horse/horse.component';
|
import { HorseComponent } from './component/horse/horse.component';
|
||||||
|
import { ListHorsesComponent } from './component/list-horses/list-horses.component'
|
||||||
import { AddHorseComponent } from './component/add-horse/add-horse.component';
|
import { AddHorseComponent } from './component/add-horse/add-horse.component';
|
||||||
import { UpdateHorseComponent } from './component/update-horse/update-horse.component';
|
import { UpdateHorseComponent } from './component/update-horse/update-horse.component';
|
||||||
|
|
||||||
@ -9,9 +10,10 @@ import { UpdateHorseComponent } from './component/update-horse/update-horse.comp
|
|||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{path: '', redirectTo: 'owner', pathMatch: 'full'},
|
{path: '', redirectTo: 'owner', pathMatch: 'full'},
|
||||||
{path: 'owner', component: OwnerComponent},
|
{path: 'owner', component: OwnerComponent},
|
||||||
{path: 'horse', component: HorseComponent},
|
{path: 'horse', component: ListHorsesComponent},
|
||||||
{path: 'horse/add', component: AddHorseComponent},
|
{path: 'horse/add', component: AddHorseComponent},
|
||||||
{path: 'horse/:id/edit', component: UpdateHorseComponent},
|
{path: 'horse/:id/edit', component: UpdateHorseComponent},
|
||||||
|
{path: 'horse/:id', component: HorseComponent},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -10,6 +10,7 @@ import {OwnerComponent} from './component/owner/owner.component';
|
|||||||
import { HorseComponent } from './component/horse/horse.component';
|
import { HorseComponent } from './component/horse/horse.component';
|
||||||
import { AddHorseComponent } from './component/add-horse/add-horse.component';
|
import { AddHorseComponent } from './component/add-horse/add-horse.component';
|
||||||
import { UpdateHorseComponent } from './component/update-horse/update-horse.component';
|
import { UpdateHorseComponent } from './component/update-horse/update-horse.component';
|
||||||
|
import { ListHorsesComponent } from './component/list-horses/list-horses.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -18,13 +19,14 @@ import { UpdateHorseComponent } from './component/update-horse/update-horse.comp
|
|||||||
OwnerComponent,
|
OwnerComponent,
|
||||||
HorseComponent,
|
HorseComponent,
|
||||||
AddHorseComponent,
|
AddHorseComponent,
|
||||||
UpdateHorseComponent
|
UpdateHorseComponent,
|
||||||
|
ListHorsesComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
FormsModule
|
FormsModule,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
bootstrap: [AppComponent]
|
bootstrap: [AppComponent]
|
||||||
|
@ -0,0 +1,79 @@
|
|||||||
|
<div *ngIf="error" class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||||
|
<strong>Error! </strong> {{ errorMessage }}
|
||||||
|
<button type="button" (click)="vanishError()" class="close" data-dismiss="alert" aria-label="Close">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<h1>Horse List</h1>
|
||||||
|
<form class="search-form" id="searchForm" (ngSubmit)="loadFilteredHorses()" #searchForm="ngForm">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<input type="text" class="form-control" name="name" [(ngModel)]="filters.name" placeholder="Name">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<input type="text" class="form-control" name="description" [(ngModel)]="filters.description" placeholder="Keywords from description">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<input type="date" class="form-control" name="birthday" [(ngModel)]="filters.birthday" required pattern="\d{4}-\d{2}-\d{2}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<select class="form-control" [(ngModel)]="filters.race" name="race">
|
||||||
|
<option value="" disabled selected>Race</option>
|
||||||
|
<option value="ARABIAN">Arabian</option>
|
||||||
|
<option value="MORGAN">Morgan</option>
|
||||||
|
<option value="PAINT">Paint</option>
|
||||||
|
<option value="APPALOOSA">Appaloosa</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col">
|
||||||
|
<select class="form-control" [(ngModel)]="filters.score" name="score">
|
||||||
|
<option value="" disabled selected>Score</option>
|
||||||
|
<option value="1">1</option>
|
||||||
|
<option value="2">2</option>
|
||||||
|
<option value="3">3</option>
|
||||||
|
<option value="4">4</option>
|
||||||
|
<option value="5">5</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-primary"><i class="fas fa-search"></i></button>
|
||||||
|
<button type="button" onclick="document.getElementById('searchForm').reset();" class="btn btn-danger"><i class="fas fa-times"></i></button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">#</th>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Description</th>
|
||||||
|
<th scope="col">Score</th>
|
||||||
|
<th scope="col">Birthday</th>
|
||||||
|
<th scope="col">Race</th>
|
||||||
|
<th scope="col">Edit</th>
|
||||||
|
<th scope="col">Delete</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let horse of horses; let id = index" scope="row">
|
||||||
|
<td>{{ id + 1 }}</td>
|
||||||
|
<td>{{ horse.name }}</td>
|
||||||
|
<td>{{ horse.description }}</td>
|
||||||
|
<td>{{ horse.score }}</td>
|
||||||
|
<td>{{ horse.birthday }}</td>
|
||||||
|
<td>{{ horse.race }}</td>
|
||||||
|
<td>
|
||||||
|
<a class="btn btn-success" href="horse/{{ horse.id }}/edit"><i class="fas fa-edit"></i></a>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<button class="btn btn-danger"><i class="fas fa-trash"></i></button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
@ -0,0 +1,8 @@
|
|||||||
|
.search-form{
|
||||||
|
margin-top: 1%;
|
||||||
|
margin-bottom: 3%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
margin-left: 0.5%;
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Horse } from '../../dto/horse';
|
||||||
|
import { HorseService } from '../../service/horse.service';
|
||||||
|
import { HttpParams } from '@angular/common/http';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-list-horses',
|
||||||
|
templateUrl: './list-horses.component.html',
|
||||||
|
styleUrls: ['./list-horses.component.scss']
|
||||||
|
})
|
||||||
|
export class ListHorsesComponent implements OnInit {
|
||||||
|
error = false;
|
||||||
|
errorMessage = '';
|
||||||
|
horses: Array<Horse>;
|
||||||
|
filters: {
|
||||||
|
name: string,
|
||||||
|
description: string,
|
||||||
|
birthday: string,
|
||||||
|
race: string,
|
||||||
|
score: string
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(private horseService: HorseService) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
// Nullify queries
|
||||||
|
this.filters = {
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
birthday: null,
|
||||||
|
race: "",
|
||||||
|
score: "",
|
||||||
|
};
|
||||||
|
this.loadAllHorses();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error flag will be deactivated, which clears the error message
|
||||||
|
*/
|
||||||
|
vanishError() {
|
||||||
|
this.error = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all horses from the database
|
||||||
|
*/
|
||||||
|
private loadAllHorses() {
|
||||||
|
console.log("GET all horses from API");
|
||||||
|
this.horseService.getAllHorses().subscribe(
|
||||||
|
(horses: Array<Horse>) => {
|
||||||
|
this.horses = horses;
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.defaultServiceErrorHandling(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load filtered horses from the database
|
||||||
|
*/
|
||||||
|
public loadFilteredHorses() {
|
||||||
|
console.log("GET filtered horses from API");
|
||||||
|
|
||||||
|
console.log(this.filters);
|
||||||
|
// Create HttpParams for the sorting
|
||||||
|
let params: HttpParams = new HttpParams();
|
||||||
|
if(this.filters.name != null && this.filters.name.length !== 0)
|
||||||
|
params = params.set('name', this.filters.name);
|
||||||
|
|
||||||
|
if(this.filters.description != null && this.filters.description.length !== 0)
|
||||||
|
params = params.set('description', this.filters.description);
|
||||||
|
|
||||||
|
if(this.filters.score != null && this.filters.score.length !== 0)
|
||||||
|
params = params.set('score', this.filters.score);
|
||||||
|
|
||||||
|
if(this.filters.birthday !== null)
|
||||||
|
params = params.set('birthday', this.filters.birthday);
|
||||||
|
|
||||||
|
if(this.filters.race !== null && this.filters.race.length !== 0)
|
||||||
|
params = params.set('race', this.filters.race);
|
||||||
|
|
||||||
|
this.horseService.getFilteredHorses(params).subscribe(
|
||||||
|
(horses: Array<Horse>) => {
|
||||||
|
this.horses = horses;
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
this.horses = null;
|
||||||
|
this.defaultServiceErrorHandling(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private defaultServiceErrorHandling(error: any) {
|
||||||
|
console.log(error);
|
||||||
|
this.error = true;
|
||||||
|
if (error.status === 0) {
|
||||||
|
// If status is 0, the backend is probably down
|
||||||
|
this.errorMessage = 'The backend seems not to be reachable';
|
||||||
|
} else if (error.error.message === 'No message available') {
|
||||||
|
// If no detailed error message is provided, fall back to the simple error name
|
||||||
|
this.errorMessage = error.error.error;
|
||||||
|
} else {
|
||||||
|
this.errorMessage = error.error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -56,6 +56,7 @@
|
|||||||
<input type="file" class="form-control-file" name="image" id="image" (change)="handleImageInput($event.target.files)" required>
|
<input type="file" class="form-control-file" name="image" id="image" (change)="handleImageInput($event.target.files)" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-success" [disabled]="!horseForm.form.valid" (submit)="updateHorse()">Submit</button>
|
<button type="submit" class="btn btn-success float-left" [disabled]="!horseForm.form.valid" (submit)="updateHorse()">Submit</button>
|
||||||
|
<a class="btn btn-light float-right" href="horse">Cancel</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
@ -1,6 +1,6 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Horse } from '../dto/horse';
|
import { Horse } from '../dto/horse';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||||
import { Globals } from '../global/globals';
|
import { Globals } from '../global/globals';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
@ -22,6 +22,23 @@ export class HorseService {
|
|||||||
return this.httpClient.get<Horse>(this.messageBaseUri + '/' + id);
|
return this.httpClient.get<Horse>(this.messageBaseUri + '/' + id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads all horses from the backend
|
||||||
|
*/
|
||||||
|
getAllHorses(): Observable<Array<Horse>> {
|
||||||
|
console.log('Load all horses');
|
||||||
|
return this.httpClient.get<Array<Horse>>(this.messageBaseUri);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all horses from the backend that correspond to a filter
|
||||||
|
* @param filter to use
|
||||||
|
*/
|
||||||
|
getFilteredHorses(filter: HttpParams): Observable<Array<Horse>> {
|
||||||
|
console.log('Load all filtered horses');
|
||||||
|
return this.httpClient.get<Array<Horse>>(this.messageBaseUri, { params: filter });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a specific horse to the backend and loads it if successful
|
* Adds a specific horse to the backend and loads it if successful
|
||||||
* @param horse
|
* @param horse
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
<base href="/">
|
<base href="/">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
|
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.2.0/css/all.css" crossorigin="anonymous">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<app-root></app-root>
|
<app-root></app-root>
|
||||||
|
Reference in New Issue
Block a user