connect formgroup with step 1 from the event wizzard

This commit is contained in:
Igor Hrenowitsch Propisnov 2024-07-30 20:52:00 +02:00
parent 95aa8d6b11
commit 4a69d916c6
6 changed files with 151 additions and 21 deletions

View File

@ -21,7 +21,7 @@
<div class="flex-grow overflow-y-auto px-4 sm:px-8 py-8"> <div class="flex-grow overflow-y-auto px-4 sm:px-8 py-8">
<div class="w-full max-w-4xl mx-auto"> <div class="w-full max-w-4xl mx-auto">
@if (currentStep() === 0) { @if (currentStep() === 0) {
<app-basic-step></app-basic-step> <app-basic-step [form]="form"></app-basic-step>
} }
@if (currentStep() === 1) { @if (currentStep() === 1) {
<app-tickets-step></app-tickets-step> <app-tickets-step></app-tickets-step>

View File

@ -4,8 +4,8 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
signal, signal,
WritableSignal, WritableSignal,
OnInit,
} from '@angular/core'; } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { BasicStepComponent } from './steps/basic-step.component'; import { BasicStepComponent } from './steps/basic-step.component';
import { TicketsStepComponent } from './steps/tickets-step.component'; import { TicketsStepComponent } from './steps/tickets-step.component';
@ -18,13 +18,19 @@ import { TicketsStepComponent } from './steps/tickets-step.component';
templateUrl: './create-event.component.html', templateUrl: './create-event.component.html',
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class CreateEventComponent implements OnInit { export class CreateEventComponent {
public currentStep: WritableSignal<number> = signal(0); public currentStep: WritableSignal<number> = signal(0);
public readonly steps: string[] = ['Basic', 'Tickets', 'Review']; public readonly steps: string[] = ['Basic', 'Tickets', 'Review'];
public form!: FormGroup;
public constructor() {} public constructor(private readonly formBuilder: FormBuilder) {
this.form = this.formBuilder.group({
public ngOnInit(): void {} eventTitle: ['', [Validators.required, Validators.minLength(3)]],
eventLocation: ['', Validators.required],
eventDate: ['', Validators.required],
eventTime: ['', Validators.required],
});
}
public getStepContent(index: number): string { public getStepContent(index: number): string {
if (index < this.currentStep()) { if (index < this.currentStep()) {

View File

@ -1,4 +1,4 @@
<form class="flex flex-col"> <form [formGroup]="form()" class="flex flex-col">
<h1 class="text-2xl font-bold mb-6">Event Basic Information</h1> <h1 class="text-2xl font-bold mb-6">Event Basic Information</h1>
<label class="form-control w-full mb-4"> <label class="form-control w-full mb-4">
@ -7,6 +7,8 @@
<span class="label-text-alt">Make it catchy and descriptive</span> <span class="label-text-alt">Make it catchy and descriptive</span>
</div> </div>
<input <input
formControlName="eventTitle"
[ngClass]="getInputClass('eventTitle')"
type="text" type="text"
placeholder="Enter your event title" placeholder="Enter your event title"
class="input input-bordered w-full" /> class="input input-bordered w-full" />
@ -18,6 +20,7 @@
<div class="form-control w-full"> <div class="form-control w-full">
<app-dropdown <app-dropdown
formControlName="eventLocation"
label="Event Location" label="Event Location"
placeholder="Search for a Location" placeholder="Search for a Location"
hintTopRight="Select or create a new Location by using the dropdown" hintTopRight="Select or create a new Location by using the dropdown"
@ -34,7 +37,12 @@
<span class="label-text">Date</span> <span class="label-text">Date</span>
<span class="label-text-alt">Select the event date</span> <span class="label-text-alt">Select the event date</span>
</div> </div>
<input type="date" id="event-date" class="input input-bordered w-full" /> <input
formControlName="eventDate"
[ngClass]="getInputClass('eventDate')"
type="date"
id="event-date"
class="input input-bordered w-full" />
<div class="label"> <div class="label">
<span class="label-text-alt"></span> <span class="label-text-alt"></span>
<span class="label-text-alt"></span> <span class="label-text-alt"></span>
@ -46,12 +54,21 @@
<span class="label-text">Time</span> <span class="label-text">Time</span>
<span class="label-text-alt">Choose the event time</span> <span class="label-text-alt">Choose the event time</span>
</div> </div>
<input type="time" id="event-time" class="input input-bordered w-full" /> <input
formControlName="eventTime"
[ngClass]="getInputClass('eventTime')"
type="time"
id="event-time"
class="input input-bordered w-full" />
<div class="label"> <div class="label">
<span class="label-text-alt"></span> <span class="label-text-alt"></span>
<span class="label-text-alt"></span> <span class="label-text-alt"></span>
</div> </div>
</label> </label>
<div>
<pre>{{ form().value | json }}</pre>
</div>
</div> </div>
</form> </form>

View File

@ -1,6 +1,12 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core'; import {
import { FormsModule } from '@angular/forms'; ChangeDetectionStrategy,
Component,
InputSignal,
ViewChild,
input,
} from '@angular/core';
import { FormGroup, FormsModule, ReactiveFormsModule } from '@angular/forms';
import { DropdownComponent } from '../../../../shared/components/dropdown/dropdown.component'; import { DropdownComponent } from '../../../../shared/components/dropdown/dropdown.component';
import { import {
@ -18,6 +24,7 @@ import {
FormsModule, FormsModule,
DropdownComponent, DropdownComponent,
LocationDialogComponent, LocationDialogComponent,
ReactiveFormsModule,
], ],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
@ -25,6 +32,7 @@ export class BasicStepComponent {
@ViewChild(LocationDialogComponent) @ViewChild(LocationDialogComponent)
public locationModal!: LocationDialogComponent; public locationModal!: LocationDialogComponent;
public items: string[] = ['Nachtigal Köln']; public items: string[] = ['Nachtigal Köln'];
public form: InputSignal<FormGroup> = input.required<FormGroup>();
public constructor() {} public constructor() {}
@ -32,8 +40,17 @@ export class BasicStepComponent {
this.openLocationModal(); this.openLocationModal();
} }
public getInputClass(controlName: string): string {
const control = this.form().get(controlName);
if (control?.touched) {
return control.valid ? 'input-success' : 'input-error';
}
return '';
}
public onLocationSubmit(location: EventLocation): void { public onLocationSubmit(location: EventLocation): void {
console.log(location); //TODO: save location
} }
private openLocationModal(): void { private openLocationModal(): void {

View File

@ -1,4 +1,4 @@
<div class="form-control w-full mb-4"> <label class="form-control w-full mb-4">
<div class="label"> <div class="label">
<span class="label-text">{{ label() }}</span> <span class="label-text">{{ label() }}</span>
<span class="label-text-alt"> <span class="label-text-alt">
@ -6,7 +6,9 @@
</span> </span>
</div> </div>
<div class="relative"> <div class="relative">
<label class="input input-bordered flex items-center gap-2"> <label
[ngClass]="getInputClass()"
class="input input-bordered flex items-center gap-2">
<input <input
type="text" type="text"
[placeholder]="placeholder()" [placeholder]="placeholder()"
@ -14,6 +16,7 @@
(focus)="onFocus()" (focus)="onFocus()"
(blur)="onBlur()" (blur)="onBlur()"
(input)="onInput($event)" (input)="onInput($event)"
(keyup.backspace)="onInputClear()"
[(ngModel)]="searchTerm" /> [(ngModel)]="searchTerm" />
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@ -112,4 +115,4 @@
<span class="label-text-alt">{{ hintBottomLeft() }}</span> <span class="label-text-alt">{{ hintBottomLeft() }}</span>
<span class="label-text-alt">{{ hintBottomRight() }}</span> <span class="label-text-alt">{{ hintBottomRight() }}</span>
</div> </div>
</div> </label>

View File

@ -1,6 +1,7 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { import {
Component, Component,
forwardRef,
input, input,
InputSignal, InputSignal,
output, output,
@ -8,15 +9,35 @@ import {
signal, signal,
WritableSignal, WritableSignal,
} from '@angular/core'; } from '@angular/core';
import { FormsModule } from '@angular/forms'; import {
AbstractControl,
ControlValueAccessor,
FormsModule,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
} from '@angular/forms';
@Component({ @Component({
selector: 'app-dropdown', selector: 'app-dropdown',
templateUrl: './dropdown.component.html', templateUrl: './dropdown.component.html',
standalone: true, standalone: true,
imports: [CommonModule, FormsModule], imports: [CommonModule, FormsModule],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DropdownComponent),
multi: true,
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => DropdownComponent),
multi: true,
},
],
}) })
export class DropdownComponent { export class DropdownComponent implements ControlValueAccessor, Validator {
public label: InputSignal<string> = input.required<string>(); public label: InputSignal<string> = input.required<string>();
public placeholder: InputSignal<string> = input.required<string>(); public placeholder: InputSignal<string> = input.required<string>();
public items: InputSignal<string[]> = input.required<string[]>(); public items: InputSignal<string[]> = input.required<string[]>();
@ -37,6 +58,36 @@ export class DropdownComponent {
public submitNewItems: OutputEmitterRef<boolean> = output<boolean>(); public submitNewItems: OutputEmitterRef<boolean> = output<boolean>();
public itemEdit: OutputEmitterRef<string> = output<string>(); public itemEdit: OutputEmitterRef<string> = output<string>();
public writeValue(value: string | null): void {
if (value && this.isValidOption(value)) {
this.searchTerm.set(value);
this.selectedItem.set(value);
} else {
this.searchTerm.set('');
this.selectedItem.set(null);
}
}
public registerOnChange(fn: (value: string) => void): void {
this.onChange = fn;
}
public registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
public validate(control: AbstractControl): ValidationErrors | null {
const value = control.value;
if (this.required() && (!value || value.trim().length === 0)) {
return { required: true };
}
if (value && value.trim().length > 0 && !this.isValidOption(value)) {
return { invalidOption: true };
}
return null;
}
public onInput(event: Event): void { public onInput(event: Event): void {
const value = (event.target as HTMLInputElement).value; const value = (event.target as HTMLInputElement).value;
@ -54,13 +105,30 @@ export class DropdownComponent {
if (exactMatch) { if (exactMatch) {
this.selectedItem.set(exactMatch); this.selectedItem.set(exactMatch);
this.onChange(exactMatch);
this.itemSelected.emit(exactMatch); this.itemSelected.emit(exactMatch);
} else { } else {
this.selectedItem.set(null); this.selectedItem.set(null);
this.onChange('');
this.itemSelected.emit(''); this.itemSelected.emit('');
} }
this.showDropdown.set(true); this.showDropdown.set(true);
this.onTouched();
}
public getInputClass(): string {
if (this.selectedItem()) {
return 'input-success';
} else if (
this.searchTerm().trim() !== '' &&
!this.isValidOption(this.searchTerm())
) {
return 'input-error';
} else if (this.required() && this.searchTerm().trim() === '') {
return 'input-error';
}
return '';
} }
public editItem(item: string, event: MouseEvent): void { public editItem(item: string, event: MouseEvent): void {
@ -71,16 +139,27 @@ export class DropdownComponent {
} }
public selectItem(item: string): void { public selectItem(item: string): void {
this.searchTerm.set(item); if (this.isValidOption(item)) {
this.selectedItem.set(item); this.searchTerm.set(item);
this.showDropdown.set(false); this.selectedItem.set(item);
this.itemSelected.emit(item); this.showDropdown.set(false);
this.itemSelected.emit(item);
this.onChange(item);
this.onTouched();
}
} }
public submitNewItem(): void { public submitNewItem(): void {
this.submitNewItems.emit(true); this.submitNewItems.emit(true);
} }
public onInputClear(): void {
this.searchTerm.set('');
this.selectedItem.set(null);
this.onChange('');
this.onTouched();
}
public onFocus(): void { public onFocus(): void {
this.showDropdown.set(true); this.showDropdown.set(true);
this.filteredItems.set(this.items()); this.filteredItems.set(this.items());
@ -89,6 +168,14 @@ export class DropdownComponent {
public onBlur(): void { public onBlur(): void {
setTimeout(() => { setTimeout(() => {
this.showDropdown.set(false); this.showDropdown.set(false);
this.onTouched();
}, 100); }, 100);
} }
private isValidOption(value: string): boolean {
return this.items().includes(value);
}
private onChange: (value: string) => void = () => {};
private onTouched: () => void = () => {};
} }