BookMonkey 3 Diff

Files changed (25) hide show
  1. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/admin-routing.module.ts +28 -0
  2. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/admin.module.ts +26 -0
  3. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/book-form/book-form.component.html +75 -0
  4. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/book-form/book-form.component.ts +119 -0
  5. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/create-book/create-book.component.html +3 -0
  6. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/create-book/create-book.component.ts +29 -0
  7. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/edit-book/edit-book.component.html +8 -0
  8. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/edit-book/edit-book.component.ts +37 -0
  9. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/form-messages/form-messages.component.html +4 -0
  10. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/form-messages/form-messages.component.ts +50 -0
  11. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/shared/book-exists-validator.service.ts +25 -0
  12. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/shared/book.validators.ts +30 -0
  13. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/app-routing.module.ts +0 -25
  14. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/app.module.ts +5 -25
  15. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/book-details/book-details.component.html +48 -0
  16. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/book-details/book-details.component.ts +37 -0
  17. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/book-list/book-list.component.html +19 -0
  18. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/book-list/book-list.component.ts +20 -0
  19. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/book-list-item/book-list-item.component.html +15 -0
  20. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/book-list-item/book-list-item.component.ts +15 -0
  21. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/books-routing.module.ts +23 -0
  22. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/books.module.ts +26 -0
  23. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/shared/delay.directive.ts +20 -0
  24. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/shared/isbn.pipe.ts +12 -0
  25. tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/shared/zoom.directive.ts +15 -0
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/admin-routing.module.ts RENAMED
@@ -0,0 +1,28 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { Routes, RouterModule } from '@angular/router';
3
+
4
+ import { EditBookComponent } from './edit-book/edit-book.component';
5
+ import { CreateBookComponent } from './create-book/create-book.component';
6
+
7
+ const routes: Routes = [
8
+ {
9
+ path: 'admin',
10
+ redirectTo: 'admin/create',
11
+ pathMatch: 'full'
12
+ },
13
+ {
14
+ path: 'admin/create',
15
+ component: CreateBookComponent
16
+ },
17
+ {
18
+ path: 'admin/edit/:isbn',
19
+ component: EditBookComponent
20
+ }
21
+ ];
22
+
23
+ @NgModule({
24
+ imports: [RouterModule.forChild(routes)],
25
+ exports: [RouterModule],
26
+ providers: []
27
+ })
28
+ export class AdminRoutingModule { }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/admin.module.ts RENAMED
@@ -0,0 +1,26 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { CommonModule } from '@angular/common';
3
+ import { ReactiveFormsModule } from '@angular/forms';
4
+ import { DateValueAccessorModule } from 'angular-date-value-accessor';
5
+
6
+ import { AdminRoutingModule } from './admin-routing.module';
7
+ import { BookFormComponent } from './book-form/book-form.component';
8
+ import { CreateBookComponent } from './create-book/create-book.component';
9
+ import { FormMessagesComponent } from './form-messages/form-messages.component';
10
+ import { EditBookComponent } from './edit-book/edit-book.component';
11
+
12
+ @NgModule({
13
+ imports: [
14
+ CommonModule,
15
+ AdminRoutingModule,
16
+ ReactiveFormsModule,
17
+ DateValueAccessorModule
18
+ ],
19
+ declarations: [
20
+ BookFormComponent,
21
+ CreateBookComponent,
22
+ EditBookComponent,
23
+ FormMessagesComponent
24
+ ]
25
+ })
26
+ export class AdminModule { }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/book-form/book-form.component.html RENAMED
@@ -0,0 +1,75 @@
1
+ <form class="ui form"
2
+ [formGroup]="bookForm"
3
+ (ngSubmit)="submitForm()">
4
+
5
+ <label>Buchtitel</label>
6
+ <input formControlName="title">
7
+ <bm-form-messages
8
+ [control]="bookForm.get('title')"
9
+ controlName="title">
10
+ </bm-form-messages>
11
+
12
+ <label>Untertitel</label>
13
+ <input formControlName="subtitle">
14
+
15
+ <label>ISBN</label>
16
+ <input formControlName="isbn">
17
+ <bm-form-messages
18
+ [control]="bookForm.get('isbn')"
19
+ controlName="isbn">
20
+ </bm-form-messages>
21
+
22
+ <label>Erscheinungsdatum</label>
23
+ <input type="date"
24
+ useValueAsDate
25
+ formControlName="published">
26
+ <bm-form-messages
27
+ [control]="bookForm.get('published')"
28
+ controlName="published">
29
+ </bm-form-messages>
30
+
31
+ <label>Autoren</label>
32
+ <button type="button" class="ui mini button"
33
+ (click)="addAuthorControl()">
34
+ + Autor
35
+ </button>
36
+ <div class="fields" formArrayName="authors">
37
+ <div class="sixteen wide field"
38
+ *ngFor="let c of authors.controls; index as i">
39
+ <input placeholder="Autor"
40
+ [formControlName]="i">
41
+ </div>
42
+ </div>
43
+ <bm-form-messages
44
+ [control]="bookForm.get('authors')"
45
+ controlName="authors">
46
+ </bm-form-messages>
47
+
48
+ <label>Beschreibung</label>
49
+ <textarea formControlName="description"></textarea>
50
+
51
+ <label>Bilder</label>
52
+ <button type="button" class="ui mini button"
53
+ (click)="addThumbnailControl()">
54
+ + Bild
55
+ </button>
56
+ <div formArrayName="thumbnails">
57
+ <div class="fields"
58
+ *ngFor="let c of thumbnails.controls; index as i"
59
+ [formGroupName]="i">
60
+ <div class="nine wide field">
61
+ <input placeholder="URL"
62
+ formControlName="url">
63
+ </div>
64
+ <div class="seven wide field">
65
+ <input placeholder="Titel"
66
+ formControlName="title">
67
+ </div>
68
+ </div>
69
+ </div>
70
+
71
+ <button class="ui button" type="submit"
72
+ [disabled]="bookForm.invalid">
73
+ Speichern
74
+ </button>
75
+ </form>
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/book-form/book-form.component.ts RENAMED
@@ -0,0 +1,119 @@
1
+ import { Component, OnInit, Input, Output, EventEmitter, OnChanges } from '@angular/core';
2
+ import { FormBuilder, FormGroup, FormArray, Validators } from '@angular/forms';
3
+
4
+ import { Book, Thumbnail } from '../../shared/book';
5
+ import { BookValidators } from '../shared/book.validators';
6
+ import { BookExistsValidatorService } from '../shared/book-exists-validator.service';
7
+
8
+ @Component({
9
+ selector: 'bm-book-form',
10
+ templateUrl: './book-form.component.html',
11
+ styleUrls: ['./book-form.component.css']
12
+ })
13
+ export class BookFormComponent implements OnInit, OnChanges {
14
+
15
+ bookForm: FormGroup;
16
+
17
+ @Input() book: Book;
18
+ @Input() editing = false;
19
+ @Output() submitBook = new EventEmitter<Book>();
20
+
21
+ constructor(
22
+ private fb: FormBuilder,
23
+ private bookExistsValidator: BookExistsValidatorService
24
+ ) { }
25
+
26
+ ngOnInit() {
27
+ this.initForm();
28
+ }
29
+
30
+ ngOnChanges() {
31
+ this.initForm();
32
+ this.setFormValues(this.book);
33
+ }
34
+
35
+ private setFormValues(book: Book) {
36
+ this.bookForm.patchValue(book);
37
+
38
+ this.bookForm.setControl(
39
+ 'authors',
40
+ this.buildAuthorsArray(book.authors)
41
+ );
42
+
43
+ this.bookForm.setControl(
44
+ 'thumbnails',
45
+ this.buildThumbnailsArray(book.thumbnails)
46
+ );
47
+ }
48
+
49
+ private initForm() {
50
+ if (this.bookForm) { return; }
51
+
52
+ this.bookForm = this.fb.group({
53
+ title: ['', Validators.required],
54
+ subtitle: [''],
55
+ isbn: [
56
+ { value: '', disabled: this.editing },
57
+ [
58
+ Validators.required,
59
+ BookValidators.isbnFormat
60
+ ],
61
+ this.editing ? null : [this.bookExistsValidator]
62
+ ],
63
+ description: [''],
64
+ authors: this.buildAuthorsArray(['']),
65
+ thumbnails: this.buildThumbnailsArray([
66
+ { title: '', url: '' }
67
+ ]),
68
+ published: []
69
+ });
70
+ }
71
+
72
+ private buildAuthorsArray(values: string[]): FormArray {
73
+ return this.fb.array(values, BookValidators.atLeastOneAuthor);
74
+ }
75
+
76
+ private buildThumbnailsArray(values: Thumbnail[]): FormArray {
77
+ return this.fb.array(
78
+ values.map(t => this.fb.group(t))
79
+ );
80
+ }
81
+
82
+ get authors(): FormArray {
83
+ return this.bookForm.get('authors') as FormArray;
84
+ }
85
+
86
+ get thumbnails(): FormArray {
87
+ return this.bookForm.get('thumbnails') as FormArray;
88
+ }
89
+
90
+ addAuthorControl() {
91
+ this.authors.push(this.fb.control(''));
92
+ }
93
+
94
+ addThumbnailControl() {
95
+ this.thumbnails.push(
96
+ this.fb.group({ url: '', title: '' })
97
+ );
98
+ }
99
+
100
+ submitForm() {
101
+ const formValue = this.bookForm.value;
102
+ const authors = formValue.authors
103
+ .filter(author => author);
104
+ const thumbnails = formValue.thumbnails
105
+ .filter(thumbnail => thumbnail.url);
106
+
107
+ const isbn = this.editing ? this.book.isbn : formValue.isbn;
108
+
109
+ const newBook: Book = {
110
+ ...formValue,
111
+ isbn,
112
+ authors,
113
+ thumbnails
114
+ };
115
+
116
+ this.submitBook.emit(newBook);
117
+ this.bookForm.reset();
118
+ }
119
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/create-book/create-book.component.html RENAMED
@@ -0,0 +1,3 @@
1
+ <h1>Buch hinzufügen</h1>
2
+
3
+ <bm-book-form (submitBook)="createBook($event)"></bm-book-form>
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/create-book/create-book.component.ts RENAMED
@@ -0,0 +1,29 @@
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { ActivatedRoute, Router } from '@angular/router';
3
+
4
+ import { Book } from '../../shared/book';
5
+ import { BookStoreService } from '../../shared/book-store.service';
6
+
7
+ @Component({
8
+ selector: 'bm-create-book',
9
+ templateUrl: './create-book.component.html',
10
+ styleUrls: ['./create-book.component.css']
11
+ })
12
+ export class CreateBookComponent implements OnInit {
13
+
14
+ constructor(
15
+ private bs: BookStoreService,
16
+ private route: ActivatedRoute,
17
+ private router: Router
18
+ ) { }
19
+
20
+ ngOnInit() {
21
+ }
22
+
23
+ createBook(book: Book) {
24
+ this.bs.create(book).subscribe(() => {
25
+ this.router.navigate(['../..', 'books'], { relativeTo: this.route });
26
+ });
27
+ }
28
+
29
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/edit-book/edit-book.component.html RENAMED
@@ -0,0 +1,8 @@
1
+ <h1>Buch bearbeiten</h1>
2
+
3
+ <bm-book-form
4
+ *ngIf="book"
5
+ (submitBook)="updateBook($event)"
6
+ [book]="book"
7
+ [editing]="true"
8
+ ></bm-book-form>
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/edit-book/edit-book.component.ts RENAMED
@@ -0,0 +1,37 @@
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { ActivatedRoute, Router } from '@angular/router';
3
+ import { map, switchMap } from 'rxjs/operators';
4
+
5
+ import { Book } from '../../shared/book';
6
+ import { BookStoreService } from '../../shared/book-store.service';
7
+
8
+ @Component({
9
+ selector: 'bm-edit-book',
10
+ templateUrl: './edit-book.component.html',
11
+ styleUrls: ['./edit-book.component.css']
12
+ })
13
+ export class EditBookComponent implements OnInit {
14
+
15
+ book: Book;
16
+
17
+ constructor(
18
+ private bs: BookStoreService,
19
+ private route: ActivatedRoute,
20
+ private router: Router
21
+ ) { }
22
+
23
+ ngOnInit() {
24
+ this.route.paramMap.pipe(
25
+ map(params => params.get('isbn')),
26
+ switchMap((isbn: string) => this.bs.getSingle(isbn))
27
+ )
28
+ .subscribe(book => this.book = book);
29
+ }
30
+
31
+ updateBook(book: Book) {
32
+ this.bs.update(book).subscribe(() => {
33
+ this.router.navigate(['../../..', 'books', book.isbn], { relativeTo: this.route });
34
+ });
35
+ }
36
+
37
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/form-messages/form-messages.component.html RENAMED
@@ -0,0 +1,4 @@
1
+ <div class="ui negative message"
2
+ *ngFor="let msg of errorsForControl()">
3
+ {{ msg }}
4
+ </div>
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/form-messages/form-messages.component.ts RENAMED
@@ -0,0 +1,50 @@
1
+ import { Component, OnInit, Input } from '@angular/core';
2
+ import { AbstractControl } from '@angular/forms';
3
+
4
+ @Component({
5
+ selector: 'bm-form-messages',
6
+ templateUrl: './form-messages.component.html',
7
+ styleUrls: ['./form-messages.component.css']
8
+ })
9
+ export class FormMessagesComponent implements OnInit {
10
+
11
+ @Input() control: AbstractControl;
12
+ @Input() controlName: string;
13
+
14
+ private allMessages = {
15
+ title: {
16
+ required: 'Ein Buchtitel muss angegeben werden.'
17
+ },
18
+ isbn: {
19
+ required: 'Es muss eine ISBN angegeben werden.',
20
+ isbnFormat: 'Die ISBN muss aus 10 oder 13 Zeichen bestehen.',
21
+ isbnExists: 'Die ISBN existiert bereits.'
22
+ },
23
+ published: {
24
+ required: 'Es muss ein Erscheinungsdatum angegeben werden.'
25
+ },
26
+ authors: {
27
+ atLeastOneAuthor: 'Es muss ein Autor angegeben werden.'
28
+ }
29
+ };
30
+
31
+ constructor() { }
32
+
33
+ ngOnInit() {
34
+ }
35
+
36
+ errorsForControl(): string[] {
37
+ const messages = this.allMessages[this.controlName];
38
+
39
+ if (
40
+ !this.control ||
41
+ !this.control.errors ||
42
+ !messages ||
43
+ !this.control.dirty
44
+ ) { return null; }
45
+
46
+ return Object.keys(this.control.errors)
47
+ .map(err => messages[err]);
48
+ }
49
+
50
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/shared/book-exists-validator.service.ts RENAMED
@@ -0,0 +1,25 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { FormControl, AsyncValidator, ValidationErrors } from '@angular/forms';
3
+ import { Observable, of } from 'rxjs';
4
+ import { map, catchError } from 'rxjs/operators';
5
+
6
+ import { BookStoreService } from '../../shared/book-store.service';
7
+
8
+ @Injectable({
9
+ providedIn: 'root'
10
+ })
11
+ export class BookExistsValidatorService implements AsyncValidator {
12
+
13
+ constructor(private bs: BookStoreService) { }
14
+
15
+ validate(
16
+ control: FormControl
17
+ ): Observable<ValidationErrors | null> {
18
+ return this.bs.check(control.value).pipe(
19
+ map(exists => (exists === false) ? null : {
20
+ isbnExists: { valid: false }
21
+ }),
22
+ catchError(() => of(null))
23
+ );
24
+ }
25
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/admin/shared/book.validators.ts RENAMED
@@ -0,0 +1,30 @@
1
+ import { FormControl, FormArray, ValidationErrors } from '@angular/forms';
2
+
3
+ export class BookValidators {
4
+
5
+ static isbnFormat(control: FormControl): ValidationErrors | null {
6
+ if (!control.value) { return null; }
7
+
8
+ const numbers = control.value.replace(/-/g, '');
9
+ const isbnPattern = /(^\d{10}$)|(^\d{13}$)/;
10
+
11
+ if (isbnPattern.test(numbers)) {
12
+ return null;
13
+ } else {
14
+ return {
15
+ isbnFormat: { valid: false }
16
+ };
17
+ }
18
+ }
19
+
20
+ static atLeastOneAuthor(controlArray: FormArray): ValidationErrors | null {
21
+ if (controlArray.controls.some(el => el.value)) {
22
+ return null;
23
+ } else {
24
+ return {
25
+ atLeastOneAuthor: { valid: false }
26
+ };
27
+ }
28
+ }
29
+
30
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/app-routing.module.ts RENAMED
@@ -2,10 +2,6 @@
2
import { Routes, RouterModule } from '@angular/router';
3
4
import { HomeComponent } from './home/home.component';
5
- import { BookListComponent } from './book-list/book-list.component';
6
- import { BookDetailsComponent } from './book-details/book-details.component';
7
- import { CreateBookComponent } from './create-book/create-book.component';
8
- import { EditBookComponent } from './edit-book/edit-book.component';
9
10
export const routes: Routes = [
11
{
@@ -16,27 +12,6 @@
16
{
17
path: 'home',
18
component: HomeComponent
19
- },
20
- {
21
- path: 'books',
22
- component: BookListComponent
23
- },
24
- {
25
- path: 'books/:isbn',
26
- component: BookDetailsComponent
27
- },
28
- {
29
- path: 'admin',
30
- redirectTo: 'admin/create',
31
- pathMatch: 'full'
32
- },
33
- {
34
- path: 'admin/create',
35
- component: CreateBookComponent
36
- },
37
- {
38
- path: 'admin/edit/:isbn',
39
- component: EditBookComponent
40
}
41
];
42
2
import { Routes, RouterModule } from '@angular/router';
3
4
import { HomeComponent } from './home/home.component';
5
6
export const routes: Routes = [
7
{
12
{
13
path: 'home',
14
component: HomeComponent
15
}
16
];
17
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/app.module.ts RENAMED
@@ -1,49 +1,29 @@
1
import { CommonModule } from '@angular/common';
2
import { NgModule, LOCALE_ID } from '@angular/core';
3
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
4
- import { ReactiveFormsModule } from '@angular/forms';
5
- import { DateValueAccessorModule } from 'angular-date-value-accessor';
6
import localeDe from '@angular/common/locales/de';
7
import { registerLocaleData } from '@angular/common';
8
9
import { AppRoutingModule } from './app-routing.module.one-app';
10
import { AppComponent } from './app.component';
11
import { HomeComponent } from './home/home.component';
12
- import { BookListComponent } from './book-list/book-list.component';
13
- import { BookListItemComponent } from './book-list-item/book-list-item.component';
14
- import { BookDetailsComponent } from './book-details/book-details.component';
15
import { SearchComponent } from './search/search.component';
16
import { TokenInterceptor } from './shared/token-interceptor';
17
- import { BookFormComponent } from './book-form/book-form.component';
18
- import { CreateBookComponent } from './create-book/create-book.component';
19
- import { FormMessagesComponent } from './form-messages/form-messages.component';
20
- import { EditBookComponent } from './edit-book/edit-book.component';
21
- import { IsbnPipe } from './shared/isbn.pipe';
22
- import { ZoomDirective } from './shared/zoom.directive';
23
- import { DelayDirective } from './shared/delay.directive';
24
25
@NgModule({
26
declarations: [
27
AppComponent,
28
HomeComponent,
29
- BookListComponent,
30
- BookListItemComponent,
31
- BookDetailsComponent,
32
- SearchComponent,
33
- BookFormComponent,
34
- CreateBookComponent,
35
- FormMessagesComponent,
36
- EditBookComponent,
37
- IsbnPipe,
38
- ZoomDirective,
39
- DelayDirective
40
],
41
imports: [
42
CommonModule,
43
HttpClientModule,
44
AppRoutingModule,
45
- ReactiveFormsModule,
46
- DateValueAccessorModule
47
],
48
providers: [
49
{ provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true },
1
import { CommonModule } from '@angular/common';
2
import { NgModule, LOCALE_ID } from '@angular/core';
3
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
4
import localeDe from '@angular/common/locales/de';
5
import { registerLocaleData } from '@angular/common';
6
7
import { AppRoutingModule } from './app-routing.module.one-app';
8
import { AppComponent } from './app.component';
9
import { HomeComponent } from './home/home.component';
10
import { SearchComponent } from './search/search.component';
11
import { TokenInterceptor } from './shared/token-interceptor';
12
+ import { BooksModule } from './books/books.module';
13
+ import { AdminModule } from './admin/admin.module';
14
15
@NgModule({
16
declarations: [
17
AppComponent,
18
HomeComponent,
19
+ SearchComponent
20
],
21
imports: [
22
CommonModule,
23
HttpClientModule,
24
AppRoutingModule,
25
+ BooksModule,
26
+ AdminModule
27
],
28
providers: [
29
{ provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true },
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/book-details/book-details.component.html RENAMED
@@ -0,0 +1,48 @@
1
+ <div *ngIf="book; else loading">
2
+ <h1>{{ book.title }}</h1>
3
+ <h3 *ngIf="book.subtitle">{{ book.subtitle }}</h3>
4
+ <div class="ui divider"></div>
5
+ <div class="ui grid">
6
+ <div class="four wide column">
7
+ <h4>Autoren</h4>
8
+ <ng-container *ngFor="let author of book.authors">
9
+ {{ author }}<br>
10
+ </ng-container>
11
+ </div>
12
+ <div class="four wide column">
13
+ <h4>ISBN</h4>
14
+ {{ book.isbn | isbn }}
15
+ </div>
16
+ <div class="four wide column">
17
+ <h4>Erschienen</h4>
18
+ {{ book.published | date:'longDate' }}
19
+ </div>
20
+ <div class="four wide column">
21
+ <h4>Rating</h4>
22
+ <ng-container
23
+ *ngFor="let r of getRating(book.rating);
24
+ index as i">
25
+ <i class="yellow star icon"
26
+ *bmDelay="500 + i * 200"></i>
27
+ </ng-container>
28
+ </div>
29
+ </div>
30
+ <h4>Beschreibung</h4>
31
+ <p>{{ book.description }}</p>
32
+ <div class="ui small images">
33
+ <img *ngFor="let thumbnail of book.thumbnails"
34
+ [src]="thumbnail.url">
35
+ </div>
36
+ <button class="ui tiny red labeled icon button"
37
+ (click)="removeBook()">
38
+ <i class="remove icon"></i> Buch löschen
39
+ </button>
40
+ <a class="ui tiny yellow labeled icon button"
41
+ [routerLink]="['../../admin/edit', book.isbn]">
42
+ <i class="write icon"></i> Buch bearbeiten
43
+ </a>
44
+ </div>
45
+
46
+ <ng-template #loading>
47
+ <div class="ui active centered inline loader"></div>
48
+ </ng-template>
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/book-details/book-details.component.ts RENAMED
@@ -0,0 +1,37 @@
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { ActivatedRoute, Router } from '@angular/router';
3
+
4
+ import { Book } from '../../shared/book';
5
+ import { BookStoreService } from '../../shared/book-store.service';
6
+
7
+ @Component({
8
+ selector: 'bm-book-details',
9
+ templateUrl: './book-details.component.html',
10
+ styleUrls: ['./book-details.component.css']
11
+ })
12
+ export class BookDetailsComponent implements OnInit {
13
+ book: Book;
14
+
15
+ constructor(
16
+ private bs: BookStoreService,
17
+ private router: Router,
18
+ private route: ActivatedRoute
19
+ ) { }
20
+
21
+ ngOnInit() {
22
+ const params = this.route.snapshot.paramMap;
23
+ this.bs.getSingle(params.get('isbn'))
24
+ .subscribe(b => this.book = b);
25
+ }
26
+
27
+ getRating(num: number) {
28
+ return new Array(num);
29
+ }
30
+
31
+ removeBook() {
32
+ if (confirm('Buch wirklich löschen?')) {
33
+ this.bs.remove(this.book.isbn)
34
+ .subscribe(res => this.router.navigate(['../'], { relativeTo: this.route }));
35
+ }
36
+ }
37
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/book-list/book-list.component.html RENAMED
@@ -0,0 +1,19 @@
1
+ <div class="ui middle aligned selection divided list">
2
+
3
+ <ng-container *ngIf="books$ | async as books; else loading">
4
+ <bm-book-list-item class="item"
5
+ *ngFor="let b of books"
6
+ [book]="b"
7
+ [routerLink]="b.isbn"></bm-book-list-item>
8
+
9
+ <p *ngIf="!books.length">Es wurden noch keine Bücher eingetragen.</p>
10
+ </ng-container>
11
+
12
+ <ng-template #loading>
13
+ <div class="ui active dimmer">
14
+ <div class="ui large text loader">Daten werden geladen...</div>
15
+ </div>
16
+ </ng-template>
17
+
18
+ </div>
19
+
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/book-list/book-list.component.ts RENAMED
@@ -0,0 +1,20 @@
1
+ import { Component, OnInit } from '@angular/core';
2
+ import { Observable } from 'rxjs';
3
+
4
+ import { Book } from '../../shared/book';
5
+ import { BookStoreService } from '../../shared/book-store.service';
6
+
7
+ @Component({
8
+ selector: 'bm-book-list',
9
+ templateUrl: './book-list.component.html',
10
+ styleUrls: ['./book-list.component.css']
11
+ })
12
+ export class BookListComponent implements OnInit {
13
+ books$: Observable<Book[]>;
14
+
15
+ constructor(private bs: BookStoreService) { }
16
+
17
+ ngOnInit() {
18
+ this.books$ = this.bs.getAll();
19
+ }
20
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/book-list-item/book-list-item.component.html RENAMED
@@ -0,0 +1,15 @@
1
+ <img class="ui tiny image"
2
+ *ngIf="book.thumbnails && book.thumbnails[0] && book.thumbnails[0].url"
3
+ [src]="book.thumbnails[0].url"
4
+ bmZoom>
5
+ <div class="content">
6
+ <div class="header">{{ book.title }}</div>
7
+ <div *ngIf="book.subtitle" class="description">{{ book.subtitle }}</div>
8
+ <div class="metadata">
9
+ <span *ngFor="let author of book.authors; last as l">
10
+ {{ author }}<span *ngIf="!l">, </span>
11
+ </span>
12
+ <br>
13
+ ISBN {{ book.isbn | isbn }}
14
+ </div>
15
+ </div>
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/book-list-item/book-list-item.component.ts RENAMED
@@ -0,0 +1,15 @@
1
+ import { Component, OnInit, Input } from '@angular/core';
2
+
3
+ import { Book } from '../../shared/book';
4
+
5
+ @Component({
6
+ selector: 'bm-book-list-item',
7
+ templateUrl: './book-list-item.component.html',
8
+ styleUrls: ['./book-list-item.component.css']
9
+ })
10
+ export class BookListItemComponent implements OnInit {
11
+ @Input() book: Book;
12
+
13
+ ngOnInit() {
14
+ }
15
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/books-routing.module.ts RENAMED
@@ -0,0 +1,23 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { Routes, RouterModule } from '@angular/router';
3
+
4
+ import { BookListComponent } from './book-list/book-list.component';
5
+ import { BookDetailsComponent } from './book-details/book-details.component';
6
+
7
+ const routes: Routes = [
8
+ {
9
+ path: 'books',
10
+ component: BookListComponent
11
+ },
12
+ {
13
+ path: 'books/:isbn',
14
+ component: BookDetailsComponent
15
+ }
16
+ ];
17
+
18
+ @NgModule({
19
+ imports: [RouterModule.forChild(routes)],
20
+ exports: [RouterModule],
21
+ providers: []
22
+ })
23
+ export class BooksRoutingModule { }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/books.module.ts RENAMED
@@ -0,0 +1,26 @@
1
+ import { DelayDirective } from './shared/delay.directive';
2
+ import { NgModule } from '@angular/core';
3
+ import { CommonModule } from '@angular/common';
4
+ import { BooksRoutingModule } from './books-routing.module';
5
+
6
+ import { BookListComponent } from './book-list/book-list.component';
7
+ import { BookListItemComponent } from './book-list-item/book-list-item.component';
8
+ import { BookDetailsComponent } from './book-details/book-details.component';
9
+ import { IsbnPipe } from './shared/isbn.pipe';
10
+ import { ZoomDirective } from './shared/zoom.directive';
11
+
12
+ @NgModule({
13
+ imports: [
14
+ CommonModule,
15
+ BooksRoutingModule
16
+ ],
17
+ declarations: [
18
+ BookListComponent,
19
+ BookListItemComponent,
20
+ BookDetailsComponent,
21
+ IsbnPipe,
22
+ ZoomDirective,
23
+ DelayDirective
24
+ ]
25
+ })
26
+ export class BooksModule { }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/shared/delay.directive.ts RENAMED
@@ -0,0 +1,20 @@
1
+ import { Directive, OnInit, Input, TemplateRef, ViewContainerRef } from '@angular/core';
2
+
3
+ @Directive({
4
+ selector: '[bmDelay]'
5
+ })
6
+ export class DelayDirective implements OnInit {
7
+ @Input() bmDelay;
8
+
9
+ constructor(
10
+ private templateRef: TemplateRef<any>,
11
+ private viewContainerRef: ViewContainerRef
12
+ ) { }
13
+
14
+ ngOnInit() {
15
+ setTimeout(() => {
16
+ this.viewContainerRef.createEmbeddedView(this.templateRef);
17
+ }, this.bmDelay);
18
+ }
19
+
20
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/shared/isbn.pipe.ts RENAMED
@@ -0,0 +1,12 @@
1
+ import { Pipe, PipeTransform } from '@angular/core';
2
+
3
+ @Pipe({
4
+ name: 'isbn'
5
+ })
6
+ export class IsbnPipe implements PipeTransform {
7
+
8
+ transform(value: string): string {
9
+ if (!value) { return null; }
10
+ return `${value.substr(0, 3)}-${value.substr(3)}`;
11
+ }
12
+ }
tmp/src/app/book-monkey/{iteration-5/directives → iteration-6/modules}/books/shared/zoom.directive.ts RENAMED
@@ -0,0 +1,15 @@
1
+ import { Directive, HostBinding, HostListener } from '@angular/core';
2
+
3
+ @Directive({
4
+ selector: '[bmZoom]'
5
+ })
6
+ export class ZoomDirective {
7
+ @HostBinding('class.small') isZoomed: boolean;
8
+
9
+ @HostListener('mouseenter') onMouseEnter() {
10
+ this.isZoomed = true;
11
+ }
12
+ @HostListener('mouseleave') onMouseLeave() {
13
+ this.isZoomed = false;
14
+ }
15
+ }