Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ButtonComponent } from "app/components/base/buttons";
import {
ComplexFormComponent, FormPageComponent, FormPickerComponent, FormSectionComponent,
} from "app/components/base/form";
import { FormFooterComponent } from "app/components/base/form/complex-form/footer";
import { ServerErrorComponent } from "app/components/base/form/server-error";
import { ServerError } from "app/models";
import { AuthorizationHttpService } from "app/services";
Expand Down Expand Up @@ -98,6 +99,7 @@ describe("ComplexFormComponent", () => {
FormPageComponent,
FormSectionComponent,
FormPickerComponent,
FormFooterComponent,
],
providers: [
{ provide: AuthorizationHttpService, useValue: null },
Expand Down Expand Up @@ -205,8 +207,8 @@ describe("ComplexFormComponent", () => {
});

it("should toggle the error when clicking the warning button", () => {
const toggleBtn = de.query(By.css(".toggle-error-btn > button"));
expect(toggleBtn).not.toBeFalsy();
const toggleBtn = de.query(By.css("bl-form-footer .toggle-error-btn > button"));
expect(toggleBtn).not.toBeFalsy("Error toggle button should be defined");

// Toggle hidden
click(toggleBtn);
Expand Down
82 changes: 60 additions & 22 deletions app/components/base/form/complex-form/complex-form.component.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import {
AfterViewInit, ChangeDetectorRef, Component, ContentChildren, HostBinding, Input, QueryList, Type,
AfterViewInit, ChangeDetectorRef, Component, ContentChildren, HostBinding, Input, OnChanges, QueryList, Type,
} from "@angular/core";
import { FormControl } from "@angular/forms";

import { Dto, autobind } from "app/core";
import { AsyncTask, Dto, autobind } from "app/core";
import { ServerError } from "app/models";
import { log } from "app/utils";
import { validJsonConfig } from "app/utils/validators";
import { Observable } from "rxjs";
import { Observable, Subscription } from "rxjs";
import { FormBase } from "../form-base";
import { FormPageComponent } from "../form-page";
import { FormActionConfig } from "./footer";

import "./complex-form.scss";

export type FormSize = "small" | "medium" | "large";
Expand All @@ -34,7 +36,7 @@ export const defaultComplexFormConfig: ComplexFormConfig = {
selector: "bl-complex-form",
templateUrl: "complex-form.html",
})
export class ComplexFormComponent extends FormBase implements AfterViewInit {
export class ComplexFormComponent extends FormBase implements AfterViewInit, OnChanges {
/**
* If the form should allow multi use. \
* If true the form will have a "Save" AND a "Save and Close" button.
Expand Down Expand Up @@ -62,6 +64,7 @@ export class ComplexFormComponent extends FormBase implements AfterViewInit {
* Needs to return an observable that will have a {ServerError} if failing.
*/
@Input() public submit: (dto?: Dto<any>) => Observable<any>;
@Input() public asyncTasks: Observable<AsyncTask[]>;

@Input() @HostBinding("class") public size: FormSize = "large";

Expand All @@ -73,8 +76,13 @@ export class ComplexFormComponent extends FormBase implements AfterViewInit {
public currentPage: FormPageComponent;
public showJsonEditor = false;
public jsonValue = new FormControl(null, null, validJsonConfig);
public waitingForAsyncTask = false;
public asyncTaskList: AsyncTask[];
public actionConfig: FormActionConfig;

private _pageStack: FormPageComponent[] = [];
private _asyncTaskSub: Subscription;
private _hasAsyncTask = false;

constructor(private changeDetector: ChangeDetectorRef) {
super();
Expand All @@ -90,14 +98,28 @@ export class ComplexFormComponent extends FormBase implements AfterViewInit {
this.changeDetector.detectChanges();
}

public get isMainWindow() {
return this.currentPage === this.mainPage;
public ngOnChanges(changes) {
if (changes.asyncTasks) {
this._listenToAsyncTasks();
}
this._buildActionConfig();
}

@autobind()
public save(): Observable<any> {
let ready;
if (this._hasAsyncTask) {
this.waitingForAsyncTask = true;
ready = this.asyncTasks.filter(x => [...x].length === 0).first().do(() => {
this.waitingForAsyncTask = false;
}).share();
} else {
ready = Observable.of(null);
}
this.loading = true;
const obs = this.submit(this.getCurrentDto());
const obs = ready.flatMap(() => {
return this.submit(this.getCurrentDto());
}).shareReplay(1);
obs.subscribe({
next: () => {
this.loading = false;
Expand Down Expand Up @@ -163,6 +185,14 @@ export class ComplexFormComponent extends FormBase implements AfterViewInit {
this.closePage();
}

public toggleJsonEditor(jsonEditor) {
if (jsonEditor) {
this.switchToJsonEditor();
} else {
this.switchToClassicForm();
}
}

public switchToJsonEditor() {
if (!this.config.jsonEditor) { return; }
const obj = this.getCurrentDto().toJS();
Expand All @@ -189,21 +219,6 @@ export class ComplexFormComponent extends FormBase implements AfterViewInit {
}
}

public get saveAndCloseText() {
return this.multiUse ? `${this.actionName} and close` : this.actionName;
}

/**
* Enabled if the formGroup is valid or there is no formGroup
*/
public get submitEnabled() {
if (this.showJsonEditor) {
return this.jsonValue.valid;
} else {
return !this.formGroup || this.formGroup.valid;
}
}

/**
* There are two cases that classic form selector in footer should be disabled
* 1. showJsonEditor variable is false which means current form is already classic form
Expand All @@ -218,4 +233,27 @@ export class ComplexFormComponent extends FormBase implements AfterViewInit {
const data = JSON.parse(this.jsonValue.value);
return new this.config.jsonEditor.dtoType(data || {});
}

private _listenToAsyncTasks() {
if (this._asyncTaskSub) {
this._asyncTaskSub.unsubscribe();
this._asyncTaskSub = null;
this._hasAsyncTask = false;
}
if (this.asyncTasks) {
this._asyncTaskSub = this.asyncTasks.subscribe((asyncTasks) => {
this.asyncTaskList = asyncTasks;
this._hasAsyncTask = asyncTasks.length > 0;
});
}
}

private _buildActionConfig() {
this.actionConfig = {
name: this.actionName,
color: this.actionColor,
cancel: this.cancelText,
multiUse: this.multiUse,
};
}
}
76 changes: 30 additions & 46 deletions app/components/base/form/complex-form/complex-form.html
Original file line number Diff line number Diff line change
@@ -1,55 +1,39 @@
<div class="content" *ngIf="currentPage">
<div class="header">
<div *ngIf="_pageStack.length > 0">
<bl-button type="wide" color="primary" [action]="closePage">Back</bl-button>
<div class="content-wrapper">
<div class="header">
<div *ngIf="_pageStack.length > 0">
<bl-button type="wide" color="primary" [action]="closePage">Back</bl-button>
</div>
<div class="main">
<h1 *ngIf="currentPage.title">{{currentPage.title}}</h1>
<p *ngIf="currentPage.subtitle">{{currentPage.subtitle}}</p>
</div>
</div>
<div class="main">
<h1 *ngIf="currentPage.title">{{currentPage.title}}</h1>
<p *ngIf="currentPage.subtitle">{{currentPage.subtitle}}</p>
<div *ngIf="!showJsonEditor" class="classic-form-container">
<form novalidate>
<ng-template [ngTemplateOutlet]="currentPage.content"></ng-template>
</form>
</div>
</div>
<div *ngIf="!showJsonEditor" class="classic-form-container">
<form novalidate>
<ng-template [ngTemplateOutlet]="currentPage.content"></ng-template>
</form>
<div class="loading-overlay" *ngIf="loading"></div>
</div>

<div *ngIf="showJsonEditor" class="json-editor-container">
<bl-form-json-editor [formControl]="jsonValue" [fileUri]="fileUri"></bl-form-json-editor>
<div *ngIf="showJsonEditor" class="json-editor-container">
<bl-form-json-editor [formControl]="jsonValue" [fileUri]="fileUri"></bl-form-json-editor>
</div>
<div class="loading-overlay" *ngIf="loading"></div>
</div>
</div>
<div class="form-server-error" *ngIf="showError">
<bl-server-error [error]="error"></bl-server-error>
</div>
<div class="form-footer" *ngIf="!hideFooter">
<div class="toggle-mode" *ngIf="config.jsonEditor">
<bl-button type="square" [disabled]="classicFormDisabled" (do)="switchToClassicForm()">
<i class="fa fa-commenting"></i>
</bl-button>
<bl-button type="square" [disabled]="showJsonEditor" (do)="switchToJsonEditor()">
<i class="fa fa-code"></i>
</bl-button>
</div>
<div class="toggle-error-btn" *ngIf="error">
<button mat-icon-button color="warn" (click)="toggleShowError()" matTooltip="There was an error submitting this form" matTooltipPosition="above">
<mat-icon fontIcon="fa-exclamation-triangle"></mat-icon>
</button>
</div>
<div class="summary">
<ng-content select="[blFormSummary]"></ng-content>
</div>
<div class="form-buttons">
<div *ngIf="isMainWindow">
<bl-button type="wide" *ngIf="multiUse" [action]="save" [disabled]="!submitEnabled" class="add">{{actionName}}</bl-button>
<bl-button type="wide" *ngIf="containerRef" [action]="saveAndClose" [disabled]="!submitEnabled" [color]="actionColor" class="add-and-close">{{saveAndCloseText}}</bl-button>
<span style="display: inline-block">
<bl-button type="wide" color="light" [disabled]="isSaving" [action]="close" class="close">{{cancelText}}</bl-button>
</span>
</div>
<div *ngIf="!isMainWindow">
<bl-button type="wide" class="cancel" color="primary" [action]="cancelPage">Cancel</bl-button>
<bl-button type="wide" class="select" color="primary" [action]="closePageOrSubmit" [disabled]="!currentPage.submitEnabled">Select</bl-button>
</div>
</div>
</div>
<bl-form-footer class="form-footer" *ngIf="!hideFooter"
[config]="config"
[waitingForAsyncTask]="waitingForAsyncTask"
[asyncTasks]="asyncTaskList"
[jsonValue]="jsonValue"
[showJsonEditor]="showJsonEditor"
[actionConfig]="actionConfig"
[currentPage]="currentPage"
[formGroup]="formGroup"
[error]="error"
[(showError)]="showError"
(showJsonEditorChanges)="toggleJsonEditor($event)">
</bl-form-footer>
54 changes: 17 additions & 37 deletions app/components/base/form/complex-form/complex-form.scss
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ bl-complex-form {
.header {
display: flex;
margin: 10px;
flex-shrink: 0;

> .main {
flex: 1;
Expand All @@ -49,18 +50,25 @@ bl-complex-form {
}

> .content {
overflow-y: auto;
// overflow-y: auto;
position: relative;
height: calc(100% - #{$footer-height});

> .loading-overlay {
position: absolute;
background: white;
opacity: 0.5;
top: 0;
left: 0;
bottom: 0;
right: 0;
> .content-wrapper {
display: flex;
flex-direction: column;
overflow-y: auto;
height: 100%;

.loading-overlay {
position: absolute;
background: white;
opacity: 0.5;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
}

.classic-form {
Expand All @@ -82,33 +90,5 @@ bl-complex-form {

> .form-footer {
height: $footer-height;

// background: map-get($primary, 500);
padding: 5px;
display: flex;
align-items: center;

.toggle-mode {
margin: 0 10px;
}

> .toggle-error-btn {
button {
font-size: 26px;
}
}

> .summary {
margin: 0 5px;
flex: 1;
}

> .form-buttons {
margin: 0 5px;

bl-button {
margin-right: 5px;
}
}
}
}
Loading