Skip to content
This repository has been archived by the owner on Jan 19, 2023. It is now read-only.

Commit

Permalink
Refactor form component and allow custom buttons in modal
Browse files Browse the repository at this point in the history
Signed-off-by: GuessWhoSamFoo <foos@vmware.com>
  • Loading branch information
GuessWhoSamFoo committed Sep 12, 2020
1 parent a580cbe commit 2e88d98
Show file tree
Hide file tree
Showing 13 changed files with 254 additions and 350 deletions.
8 changes: 8 additions & 0 deletions pkg/view/component/modal.go
Expand Up @@ -21,6 +21,7 @@ type ModalConfig struct {
Form *Form `json:"form,omitempty"`
Opened bool `json:"opened"`
ModalSize ModalSize `json:"size,omitempty"`
Buttons []Button `json:"buttons,omitempty"`
}

// UnmarshalJSON unmarshals a modal config from JSON.
Expand All @@ -30,6 +31,7 @@ func (m *ModalConfig) UnmarshalJSON(data []byte) error {
Form *Form `json:"form,omitempty"`
Opened bool `json:"opened"`
ModalSize ModalSize `json:"size,omitempty"`
Buttons []Button `json:"buttons,omitempty"`
}{}

if err := json.Unmarshal(data, &x); err != nil {
Expand All @@ -47,6 +49,7 @@ func (m *ModalConfig) UnmarshalJSON(data []byte) error {
m.Form = x.Form
m.Opened = x.Opened
m.ModalSize = x.ModalSize
m.Buttons = x.Buttons
return nil
}

Expand Down Expand Up @@ -82,6 +85,11 @@ func (m *Modal) SetSize(size ModalSize) {
m.Config.ModalSize = size
}

// AddButton is a helper to add a custom button
func (m *Modal) AddButton(button Button) {
m.Config.Buttons = append(m.Config.Buttons, button)
}

// Open opens a modal. A modal is closed by default.
func (m *Modal) Open() {
m.Config.Opened = true
Expand Down
@@ -1,45 +1,56 @@
<ng-container
*ngTemplateOutlet="currentAction ? action : content"
></ng-container>
<div class="card">
<div class="card-block">
<ng-container
*ngTemplateOutlet="currentAction ? action : content"
></ng-container>
</div>
<ng-container *ngTemplateOutlet="currentAction ? formFooter : actionFooter"></ng-container>
</div>


<ng-template #action>
<h3 class="card-title">{{ currentAction.title }}</h3>
<app-form
#appForm
[form]="currentAction.form"
[title]="currentAction.title"
(submit)="onActionSubmit($event)"
(cancel)="onActionCancel()"
>
</app-form>
</ng-template>

<ng-template #content>
<div class="card">
<div class="card-block">
<ng-container *ngIf="v.config.alert">
<div class="alert alert-{{ v.config.alert.type }} alert-sm">
<div class="alert-item static">
<span class="alert-text">
{{ v.config.alert.message }}
</span>
</div>
</div>
</ng-container>
<h4 class="card-title">
<app-view-title [views]="title"></app-view-title>
</h4>
<ng-template #formFooter>
<div class="card-footer">
<button class="btn btn-primary btn-sm" type="submit" (click)="onActionSubmit()">Submit</button>
<button class="btn btn-sm" type="button" (click)="onActionCancel()">
Cancel
</button>
</div>
</ng-template>

<div class="card-text">
<app-view-container [view]="body"></app-view-container>
<ng-template #actionFooter>
<div class="card-footer" *ngIf="v.config.actions?.length > 0">
<ng-container *ngFor="let action of v.config.actions; trackBy: trackByFn">
<button class="btn btn-sm btn-link" (click)="setAction(action)">
{{ action.name }}
</button>
</ng-container>
</div>
</ng-template>

<ng-template #content>
<ng-container *ngIf="v.config.alert">
<div class="alert alert-{{ v.config.alert.type }} alert-sm">
<div class="alert-item static">
<span class="alert-text">
{{ v.config.alert.message }}
</span>
</div>
</div>
</ng-container>
<h4 class="card-title">
<app-view-title [views]="title"></app-view-title>
</h4>

<div class="card-footer" *ngIf="v.config.actions?.length > 0">
<ng-container *ngFor="let action of v.config.actions; trackBy: trackByFn">
<button class="btn btn-sm btn-link" (click)="setAction(action)">
{{ action.name }}
</button>
</ng-container>
</div>
<div class="card-text">
<app-view-container [view]="body"></app-view-container>
</div>
</ng-template>
Expand Up @@ -12,10 +12,13 @@ import { FormBuilder, FormGroup } from '@angular/forms';
import { ViewService } from '../../../services/view/view.service';
import { viewServiceStub } from 'src/app/testing/view-service.stub';
import { SharedModule } from '../../../shared.module';
import { FormComponent } from '../form/form.component';

describe('CardComponent', () => {
let component: CardComponent;
let fixture: ComponentFixture<CardComponent>;
let formComponent: FormComponent;
let formFixture: ComponentFixture<FormComponent>;
const formBuilder: FormBuilder = new FormBuilder();

const action: Action = {
Expand Down Expand Up @@ -83,12 +86,17 @@ describe('CardComponent', () => {
formGroupExample: 'justForTest',
});

component.onActionSubmit(formGroup);
formFixture = TestBed.createComponent(FormComponent);
formComponent = formFixture.componentInstance;
formComponent.formGroup = formGroup;
component.appForm = formComponent;

component.onActionSubmit();
expect(component.currentAction).toBeUndefined();
});

it('should not submit action', () => {
component.onActionSubmit({} as FormGroup);
component.onActionSubmit();
expect(component.currentAction).toBeDefined();
});

Expand Down Expand Up @@ -117,10 +125,7 @@ describe('CardComponent', () => {

it('should call "onActionCancel" method when cancelling the form', fakeAsync(() => {
spyOn(component, 'onActionCancel');
fixture.detectChanges();
component.appForm.onFormCancel();
tick();
fixture.detectChanges();
component.onActionCancel();
expect(component.onActionCancel).toHaveBeenCalled();
}));
});
@@ -1,6 +1,5 @@
import { Component, Input, ViewChild } from '@angular/core';
import { Component, ViewChild } from '@angular/core';
import { Action, CardView, TitleView, View } from '../../../models/content';
import { FormGroup } from '@angular/forms';
import { ActionService } from '../../../services/action/action.service';
import { FormComponent } from '../form/form.component';
import { AbstractViewComponent } from '../../abstract-view/abstract-view.component';
Expand Down Expand Up @@ -28,9 +27,9 @@ export class CardComponent extends AbstractViewComponent<CardView> {
this.body = this.v.config.body;
}

onActionSubmit(formGroup: FormGroup) {
if (formGroup && formGroup.value) {
this.actionService.perform(formGroup.value);
onActionSubmit() {
if (this.appForm?.formGroup && this.appForm?.formGroup.value) {
this.actionService.perform(this.appForm.formGroup.value);
this.currentAction = undefined;
}
}
Expand Down
@@ -1,95 +1,91 @@
<form clrForm [formGroup]="formGroup" (ngSubmit)="onFormSubmit()">
<div class="card">
<div class="card-block">
<h3 class="card-title">{{ title }}</h3>
<ng-container *ngFor="let field of form.fields">
<ng-container [ngSwitch]="field.type">
<ng-container *ngSwitchCase="'checkbox'">
<clr-checkbox-container>
<label [for]="field.name">{{ field.label }}</label>
<clr-checkbox-wrapper
*ngFor="let opt of fieldChoices(field); trackBy: trackByFn"
>
<input
type="checkbox"
clrCheckbox
[formControlName]="field.name"
[value]="opt.value"
[checked]="opt.checked"
/>
<label>{{ opt.label }}</label>
</clr-checkbox-wrapper>
</clr-checkbox-container>
</ng-container>
<ng-container *ngSwitchCase="'radio'">
<clr-radio-container>
<label [for]="field.name">{{ field.label }}</label>
<clr-radio-wrapper
*ngFor="let opt of fieldChoices(field); trackBy: trackByFn"
>
<input
type="radio"
clrRadio
[formControlName]="field.name"
[value]="opt.value"
/>
<label>{{ opt.label }}</label>
</clr-radio-wrapper>
</clr-radio-container>
</ng-container>
<ng-container *ngSwitchCase="'text'">
<clr-input-container>
<label [for]="field.name">{{ field.label }}</label>
<input clrInput type="text" [formControlName]="field.name" />
</clr-input-container>
</ng-container>
<ng-container *ngSwitchCase="'number'">
<clr-input-container>
<label [for]="field.name">{{ field.label }}</label>
<input clrInput type="number" [formControlName]="field.name" />
</clr-input-container>
</ng-container>
<ng-container *ngSwitchCase="'password'">
<clr-input-container>
<label [for]="field.name">{{ field.label }}</label>
<input clrInput type="password" [formControlName]="field.name" />
</clr-input-container>
</ng-container>
<ng-container *ngSwitchCase="'select'">
<clr-select-container>
<label [for]="field.name">{{ field.label }}</label>
<select
clrSelect
[formControlName]="field.name"
[multiple]="field.configuration.multiple"
>
<option
*ngFor="let opt of fieldChoices(field); trackBy: trackByFn"
[value]="opt.value"
>
{{ opt.label }}
</option>
</select>
</clr-select-container>
</ng-container>
<ng-container *ngSwitchCase="'textarea'">
<clr-textarea-container>
<label [for]="field.name">{{ field.label }}</label>
<textarea clrTextarea [formControlName]="field.name"></textarea>
</clr-textarea-container>
</ng-container>
<ng-container *ngSwitchCase="'hidden'"> </ng-container>
<ng-container *ngSwitchDefault>
Unable to display form field type {{ field.type }}
</ng-container>
</ng-container>
<form clrForm [formGroup]="formGroup" >
<ng-container *ngFor="let field of form.fields; trackBy: trackByFn">
<ng-container [ngSwitch]="field.type">
<ng-container *ngSwitchCase="'checkbox'">
<clr-checkbox-container>
<label [for]="field.name">{{ field.label }}</label>
<clr-checkbox-wrapper
*ngFor="let opt of fieldChoices(field); trackBy: trackByFn"
>
<input
type="checkbox"
clrCheckbox
[formControlName]="field.name"
[value]="opt.value"
[checked]="opt.checked"
/>
<label>{{ opt.label }}</label>
</clr-checkbox-wrapper>
<clr-control-error>{{ field.error }}</clr-control-error>
</clr-checkbox-container>
</ng-container>
</div>
<div class="card-footer">
<button class="btn btn-primary btn-sm" type="submit">Submit</button>
<button class="btn btn-sm" type="button" (click)="onFormCancel()">
Cancel
</button>
</div>
</div>
<ng-container *ngSwitchCase="'radio'">
<clr-radio-container>
<label [for]="field.name">{{ field.label }}</label>
<clr-radio-wrapper
*ngFor="let opt of fieldChoices(field); trackBy: trackByFn"
>
<input
type="radio"
clrRadio
[formControlName]="field.name"
[value]="opt.value"
/>
<label>{{ opt.label }}</label>
</clr-radio-wrapper>
<clr-control-error>{{ field.error }}</clr-control-error>
</clr-radio-container>
</ng-container>
<ng-container *ngSwitchCase="'text'">
<clr-input-container>
<label [for]="field.name">{{ field.label }}</label>
<input clrInput type="text" [formControlName]="field.name" />
<clr-control-error>{{ field.error }}</clr-control-error>
</clr-input-container>
</ng-container>
<ng-container *ngSwitchCase="'number'">
<clr-input-container>
<label [for]="field.name">{{ field.label }}</label>
<input clrInput type="number" [formControlName]="field.name" />
<clr-control-error>{{ field.error }}</clr-control-error>
</clr-input-container>
</ng-container>
<ng-container *ngSwitchCase="'password'">
<clr-input-container>
<label [for]="field.name">{{ field.label }}</label>
<input clrInput type="password" [formControlName]="field.name" />
<clr-control-error>{{ field.error }}</clr-control-error>
</clr-input-container>
</ng-container>
<ng-container *ngSwitchCase="'select'">
<clr-select-container>
<label [for]="field.name">{{ field.label }}</label>
<select
clrSelect
[formControlName]="field.name"
[multiple]="field.configuration.multiple"
>
<option
*ngFor="let opt of fieldChoices(field); trackBy: trackByFn"
[value]="opt.value"
>
{{ opt.label }}
</option>
</select>
<clr-control-error>{{ field.error }}</clr-control-error>
</clr-select-container>
</ng-container>
<ng-container *ngSwitchCase="'textarea'">
<clr-textarea-container>
<label [for]="field.name">{{ field.label }}</label>
<textarea clrTextarea [formControlName]="field.name"></textarea>
<clr-control-error>{{ field.error }}</clr-control-error>
</clr-textarea-container>
</ng-container>
<ng-container *ngSwitchCase="'hidden'"> </ng-container>
<ng-container *ngSwitchDefault>
Unable to display form field type {{ field.type }}
</ng-container>
</ng-container>
</ng-container>
</form>
Expand Up @@ -25,8 +25,6 @@ describe('FormComponent', () => {
component.form = {
fields: [],
};
component.title = 'Title';

fixture.detectChanges();
});

Expand Down

0 comments on commit 2e88d98

Please sign in to comment.