Skip to content

Commit

Permalink
feat(form): configurable forms
Browse files Browse the repository at this point in the history
  • Loading branch information
cbourget authored and mbarbeau committed Feb 20, 2019
1 parent 7b4e90c commit abad9ab
Show file tree
Hide file tree
Showing 32 changed files with 877 additions and 0 deletions.
1 change: 1 addition & 0 deletions demo/src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ <h1>{{title}}</h1>

<a mat-list-item routerLink="dynamic-component">Dynamic Component</a>
<a mat-list-item routerLink="entity-table">Entity Table</a>
<a mat-list-item routerLink="form">Form</a>

<hr>

Expand Down
2 changes: 2 additions & 0 deletions demo/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { AppRequestModule } from './core/request/request.module';

import { AppDynamicComponentModule } from './common/dynamic-component/dynamic-component.module';
import { AppEntityTableModule } from './common/entity-table/entity-table.module';
import { AppFormModule } from './common/form/form.module';

import { AppAuthFormModule } from './auth/auth-form/auth-form.module';

Expand Down Expand Up @@ -60,6 +61,7 @@ import { AppComponent } from './app.component';

AppDynamicComponentModule,
AppEntityTableModule,
AppFormModule,

AppAuthFormModule,

Expand Down
15 changes: 15 additions & 0 deletions demo/src/app/common/form/form-routing.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Routes, RouterModule } from '@angular/router';
import { ModuleWithProviders } from '@angular/core';

import { AppFormComponent } from './form.component';

const routes: Routes = [
{
path: 'form',
component: AppFormComponent
}
];

export const AppFormRoutingModule: ModuleWithProviders = RouterModule.forChild(
routes
);
33 changes: 33 additions & 0 deletions demo/src/app/common/form/form.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<mat-card>
<mat-card-subtitle>Common</mat-card-subtitle>
<mat-card-title>Form</mat-card-title>
<mat-card-content>
See the <a href="https://github.com/infra-geo-ouverte/igo2-lib/tree/master/demo/src/app/common/form">code of this example</a>
</mat-card-content>
</mat-card>

<igo-form
*ngIf="form$ | async as form"
[form]="form"
[formData]="data$ | async"
(submitForm)="onSubmit($event)">

<igo-form-group [group]="form.groups[0]"></igo-form-group>

<div formButtons>
<button
mat-button
type="button"
color="primary"
(click)="fillForm()">
Fill Form
</button>
<button
mat-flat-button
type="submit"
color="primary"
[disabled]="submitDisabled">
Submit
</button>
</div>
</igo-form>
12 changes: 12 additions & 0 deletions demo/src/app/common/form/form.component.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pre,
code {
font-family: monospace, monospace;
}
pre {
overflow: auto;
}
pre > code {
display: block;
padding: 1rem;
word-wrap: normal;
}
83 changes: 83 additions & 0 deletions demo/src/app/common/form/form.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Validators } from '@angular/forms';
import { BehaviorSubject, Subscription } from 'rxjs';

import { Form, FormService } from '@igo2/common';

@Component({
selector: 'app-form',
templateUrl: './form.component.html',
styleUrls: ['./form.component.scss']
})
export class AppFormComponent implements OnInit, OnDestroy {

form$ = new BehaviorSubject<Form>(undefined);

data$ = new BehaviorSubject<{[key: string]: any}>(undefined);

submitDisabled: boolean = true;

private valueChanges$$: Subscription;

constructor(private formService: FormService) {}

ngOnInit() {
const fieldConfigs = [
{
name: 'id',
title: 'ID',
options:  {
cols: 1,
validator: Validators.required
}
},
{
name: 'name',
title: 'Name',
options:  {
cols: 1,
validator: Validators.required
}
},
{
name: 'status',
title: 'Status',
type: 'select',
options:  {
cols: 2
},
inputs: {
choices: [
{value: 1, title: 'Single'},
{value: 2, title: 'Married'}
]
}
}
];

const fields = fieldConfigs.map((config) => this.formService.field(config));
const form = this.formService.form([], [this.formService.group({name: 'info'}, fields)]);

this.valueChanges$$ = form.control.valueChanges.subscribe(() => {
this.submitDisabled = !form.control.valid;
});

this.form$.next(form);
}

ngOnDestroy() {
this.valueChanges$$.unsubscribe();
}

fillForm() {
this.data$.next({
id: 1,
name: 'Bob',
status: 2
});
}

onSubmit(data: {[key: string]: any}) {
alert(JSON.stringify(data));
}
}
21 changes: 21 additions & 0 deletions demo/src/app/common/form/form.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatButtonModule, MatCardModule } from '@angular/material';

import { IgoLibFormModule } from '@igo2/common';

import { AppFormComponent } from './form.component';
import { AppFormRoutingModule } from './form-routing.module';

@NgModule({
declarations: [AppFormComponent],
imports: [
CommonModule,
AppFormRoutingModule,
MatButtonModule,
MatCardModule,
IgoLibFormModule
],
exports: [AppFormComponent]
})
export class AppFormModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<mat-form-field>
<mat-select
[required]="required"
[placeholder]="placeholder"
[formControl]="formControl">
<mat-option *ngFor="let choice of choices$ | async" [value]="choice.value">
{{choice.title}}
</mat-option>
</mat-select>
</mat-form-field>
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
Input,
Component,
ChangeDetectionStrategy,
} from '@angular/core';
import { FormControl } from '@angular/forms';

import { Observable, of } from 'rxjs';

import { formControlIsRequired } from '../shared/form.utils';
import { FormFieldSelectChoice } from '../shared/form.interfaces';
import { FormFieldComponent } from '../shared/form-field-component';

/**
* This component renders a select field
*/
@FormFieldComponent('select')
@Component({
selector: 'igo-form-field-select',
templateUrl: './form-field-select.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FormFieldSelectComponent {

public choices$: Observable<FormFieldSelectChoice[]>;

/**
* The field's form control
*/
@Input() formControl: FormControl;

/**
* Field placeholder
*/
@Input() placeholder: string;

/**
* Select input choices
*/
@Input()
set choices(value: Observable<FormFieldSelectChoice[]> | FormFieldSelectChoice[]) {
if (value instanceof Observable) {
this.choices$ = value;
} else {
this.choices$ = of(value);
}
}

/**
* Whether the field is required
*/
get required(): boolean {
return formControlIsRequired(this.formControl);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@


<mat-form-field>
<input
matInput
[required]="required"
[placeholder]="placeholder"
[formControl]="formControl">
</mat-form-field>
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
Input,
Component,
ChangeDetectionStrategy,
} from '@angular/core';
import { FormControl } from '@angular/forms';

import { formControlIsRequired } from '../shared/form.utils';
import { FormFieldComponent } from '../shared/form-field-component';

/**
* This component renders a text field
*/
@FormFieldComponent('text')
@Component({
selector: 'igo-form-field-text',
templateUrl: './form-field-text.component.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FormFieldTextComponent {

/**
* The field's form control
*/
@Input() formControl: FormControl;

/**
* Field placeholder
*/
@Input() placeholder: string;

/**
* Whether the field is required
*/
get required(): boolean {
return formControlIsRequired(this.formControl);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@


<ng-container *ngIf="field !== undefined">
<igo-dynamic-outlet
[component]="getFieldComponent()"
[inputs]="getFieldInputs()"
[subscribers]="getFieldSubscribers()">
</igo-dynamic-outlet>
</ng-container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
:host {
display: block;
}
44 changes: 44 additions & 0 deletions projects/common/src/lib/form/form-field/form-field.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
Component,
Input,
ChangeDetectionStrategy
} from '@angular/core';

import { FormField, FormFieldInputs } from '../shared/form.interfaces';
import { FormFieldService } from '../shared/form-field.service';

/**
* This component renders the proper form input based on
* the field configuration it receives.
*/
@Component({
selector: 'igo-form-field',
templateUrl: './form-field.component.html',
styleUrls: ['./form-field.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class FormFieldComponent {

/**
* Field configuration
*/
@Input() field: FormField;

constructor(private formFieldService: FormFieldService) {}

getFieldComponent(): any {
return this.formFieldService.getFieldByType(this.field.type || 'text');
}

getFieldInputs(): FormFieldInputs {
return Object.assign(
{placeholder: this.field.title},
Object.assign({}, this.field.inputs || {}),
{formControl: this.field.control}
);
}

getFieldSubscribers(): {[key: string]: ({field: FormField, control: FormControl}) => void } {
return Object.assign({}, this.field.subscribers || {});
}
}
Loading

0 comments on commit abad9ab

Please sign in to comment.