Compare commits
10 Commits
8c6da2e28f
...
37eb1e37ee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37eb1e37ee | ||
|
|
cc49a89ea4 | ||
|
|
1db9e839d3 | ||
|
|
05fd2586fd | ||
|
|
3a36f948d1 | ||
|
|
abdcfd31d1 | ||
|
|
fc2c3f029a | ||
|
|
006f618c36 | ||
|
|
fe28064e10 | ||
|
|
720f8df85f |
2
.idea/modules.xml
generated
2
.idea/modules.xml
generated
@@ -2,7 +2,7 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<modules>
|
||||||
<module fileurl="file://$PROJECT_DIR$/.idea/AngularCounterInput.iml" filepath="$PROJECT_DIR$/.idea/AngularCounterInput.iml" />
|
<module fileurl="file://$PROJECT_DIR$/.idea/AngularDynFormModule.iml" filepath="$PROJECT_DIR$/.idea/AngularDynFormModule.iml" />
|
||||||
</modules>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
2
.idea/typescript-compiler.xml
generated
2
.idea/typescript-compiler.xml
generated
@@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="TypeScriptCompiler">
|
<component name="TypeScriptCompiler">
|
||||||
<option name="isCompilerEnabled" value="true" />
|
|
||||||
<option name="useConfig" value="true" />
|
<option name="useConfig" value="true" />
|
||||||
|
<option name="showAllErrors" value="true" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
1114
.idea/workspace.xml
generated
1114
.idea/workspace.xml
generated
File diff suppressed because it is too large
Load Diff
@@ -1 +1,5 @@
|
|||||||
|
<button (click)="loadValues()">Refesh</button>
|
||||||
|
<hr>
|
||||||
|
<h1>{{formName}}</h1>
|
||||||
|
<p>{{formDescription}}</p>
|
||||||
<dyn-form [questions]="formQuestions" (onSubmit)="submit($event)" [value]="formValue" [type]="'insert'"></dyn-form>
|
<dyn-form [questions]="formQuestions" (onSubmit)="submit($event)" [value]="formValue" [type]="'insert'"></dyn-form>
|
||||||
@@ -1,23 +1,48 @@
|
|||||||
import {Component, Inject} from '@angular/core';
|
import {Component, Input, OnChanges, SimpleChanges} from '@angular/core';
|
||||||
import {QuestionInterface} from './modules/dyn-form/types/question.interface';
|
import {QuestionInterface} from './modules/dyn-form/types/question.interface';
|
||||||
import {QuestionService} from './modules/dyn-form/services/question.service';
|
import {FormService} from './modules/dyn-form/services/form.service';
|
||||||
|
import {ValueService} from './modules/dyn-form/services/value.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'my-app',
|
selector: 'my-app',
|
||||||
templateUrl: './app.component.html'
|
templateUrl: './app.component.html'
|
||||||
})
|
})
|
||||||
|
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
public formQuestions: QuestionInterface[] = [];
|
public formQuestions: QuestionInterface[] = null;
|
||||||
|
public formValue: Object = null;
|
||||||
|
public formName: string = "";
|
||||||
|
public formDescription ="";
|
||||||
|
private uri: string = '/crf/fields';
|
||||||
|
|
||||||
constructor(private questionService: QuestionService) {
|
constructor(private questionService: FormService, private valueService: ValueService) {
|
||||||
this.questionService.getQuestions((res, err) => {
|
this.loadValues();
|
||||||
this.formQuestions = res;
|
}
|
||||||
|
|
||||||
|
private loadValues() {
|
||||||
|
console.log(this.uri);
|
||||||
|
this.questionService.getForm(this.uri, (res, err) => {
|
||||||
|
if (err)
|
||||||
|
console.error(err);
|
||||||
|
else {
|
||||||
|
this.formQuestions = res.questions;
|
||||||
|
this.formName = res.name;
|
||||||
|
this.formDescription = res.description;
|
||||||
|
console.log("questions", this.formQuestions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.valueService.getValue(this.uri, 3, (res, err) => {
|
||||||
|
if (err)
|
||||||
|
console.error(err);
|
||||||
|
else {
|
||||||
|
this.formValue = res;
|
||||||
|
console.log("values", this.formValue);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public submit(value: any) {
|
||||||
submit(value: any) {
|
|
||||||
console.log('send', value);
|
console.log('send', value);
|
||||||
|
this.valueService.postValues(this.uri, value, (data, err) => console.log("posted", data, err));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
|
import {Component, EventEmitter, Input, OnChanges, Output, SimpleChanges} from '@angular/core';
|
||||||
import {QuestionInterface} from './types/question.interface';
|
import {QuestionInterface} from './types/question.interface';
|
||||||
import {FormControl, FormGroup} from '@angular/forms';
|
import {FormControl, FormGroup} from '@angular/forms';
|
||||||
|
|
||||||
@@ -6,33 +6,52 @@ import {FormControl, FormGroup} from '@angular/forms';
|
|||||||
selector: 'dyn-form',
|
selector: 'dyn-form',
|
||||||
template: `
|
template: `
|
||||||
<form (ngSubmit)="submit()" [formGroup]="form">
|
<form (ngSubmit)="submit()" [formGroup]="form">
|
||||||
<dyn-question *ngFor="let question of questions" [hidden]="question.properties.methods && question.properties.methods.indexOf(type)==-1" [question]="question" [form]="form" [type]="type"></dyn-question>
|
<div *ngFor="let question of questions">
|
||||||
|
<dyn-question *ngIf="!(question.properties.methods && question.properties.methods.indexOf(type)==-1 || question.properties.spezialization && form.value.type!=question.properties.spezialization)" [ngSwitch]="question.type"
|
||||||
|
[question]="question" [form]="form" [type]="type" (onChange)="valueChange.emit(form.value)"></dyn-question>
|
||||||
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<button type="submit">Save</button>
|
<button [disabled]="form.invalid && type!='delete'" type="submit">Save</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
export class DynFormComponent implements OnInit {
|
export class DynFormComponent implements OnChanges {
|
||||||
@Input() questions: QuestionInterface[];
|
@Input() questions: QuestionInterface[];
|
||||||
@Input() value: any;
|
@Input() value: {[_:string]: any};
|
||||||
@Input() type: "'insert'|'update'|'delete'|'view'";
|
@Input() type: 'insert'|'update'|'delete'|'view';
|
||||||
@Output() valueChange: EventEmitter<any> = new EventEmitter<any>();
|
@Output() valueChange: EventEmitter<any> = new EventEmitter<any>();
|
||||||
@Output() onSubmit: EventEmitter<any> = new EventEmitter<any>();
|
@Output() onSubmit: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
|
||||||
private form: FormGroup;
|
private form: FormGroup;
|
||||||
|
|
||||||
public ngOnInit () {
|
public ngOnChanges(changes: SimpleChanges) {
|
||||||
let controls = {};
|
if (changes['questions']) {
|
||||||
for(let i = 0; i < this.questions.length; i++) {
|
let controls = {};
|
||||||
let key = this.questions[i].properties.key;
|
for(let i = 0; this.questions && i < this.questions.length; i++) {
|
||||||
controls[key] = new FormControl(this.value[key]);
|
let key = this.questions[i].properties.key;
|
||||||
|
controls[key] = new FormControl();
|
||||||
|
}
|
||||||
|
this.form = new FormGroup(controls);
|
||||||
|
}
|
||||||
|
if ((changes['questions'] || changes['value']) && this.form && this.value) {
|
||||||
|
this.form.patchValue(this.value);
|
||||||
}
|
}
|
||||||
this.form = new FormGroup(controls);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private submit() {
|
private submit() {
|
||||||
console.log("isValid", this.form.valid);
|
if (this.form.valid || this.type=='delete') {
|
||||||
this.onSubmit.emit(this.form.value);
|
let vals = {};
|
||||||
|
let type: string = this.form.value.type;
|
||||||
|
for (let question of this.questions) {
|
||||||
|
let specType = question.properties.specialization;
|
||||||
|
let methods = question.properties.methods;
|
||||||
|
let key = question.properties.key;
|
||||||
|
if (!(methods && methods.indexOf(this.type)==-1 || specType && type!=specType) && (this.type!='delete' || question.type=='hidden')) {
|
||||||
|
vals[key] = this.form.value[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.onSubmit.emit(vals);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,10 +4,15 @@ import { NgModule } from '@angular/core';
|
|||||||
import { DynFormComponent } from './dyn-form.component';
|
import { DynFormComponent } from './dyn-form.component';
|
||||||
import {DynQuestionComponent} from './dyn-question.component';
|
import {DynQuestionComponent} from './dyn-question.component';
|
||||||
import {CounterInputComponent} from './inputs/counter-input.component';
|
import {CounterInputComponent} from './inputs/counter-input.component';
|
||||||
import {KeysPipe, TagInputComponent} from './inputs/tag-input.component';
|
import {TagInputComponent} from './inputs/tag-input.component';
|
||||||
import {HiddenInputComponent} from './inputs/hidden-input.component';
|
import {HiddenInputComponent} from './inputs/hidden-input.component';
|
||||||
import {DropdownInputComponent} from './inputs/dropdown-input.component';
|
import {DropdownInputComponent} from './inputs/dropdown-input.component';
|
||||||
import {QuestionService} from './services/question.service';
|
import {FormService} from './services/form.service';
|
||||||
|
import {TextareaInputComponent} from './inputs/textarea-input.component';
|
||||||
|
import {ValueService} from './services/value.service';
|
||||||
|
import {KeysPipe} from './types/keys.pipe';
|
||||||
|
import {TextboxInputComponent} from './inputs/textbox-input.component';
|
||||||
|
import {CheckboxInputComponent} from './inputs/checkbox-input.component';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@@ -25,10 +30,14 @@ import {QuestionService} from './services/question.service';
|
|||||||
TagInputComponent,
|
TagInputComponent,
|
||||||
HiddenInputComponent,
|
HiddenInputComponent,
|
||||||
DropdownInputComponent,
|
DropdownInputComponent,
|
||||||
|
TextareaInputComponent,
|
||||||
|
TextboxInputComponent,
|
||||||
|
CheckboxInputComponent,
|
||||||
KeysPipe
|
KeysPipe
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
QuestionService
|
FormService,
|
||||||
|
ValueService
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class DynFormModule { }
|
export class DynFormModule { }
|
||||||
|
|||||||
@@ -1,27 +1,48 @@
|
|||||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||||
import {QuestionInterface} from './types/question.interface';
|
import {QuestionInterface} from './types/question.interface';
|
||||||
import {FormGroup} from '@angular/forms';
|
import {FormGroup, ValidationErrors} from '@angular/forms';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dyn-question',
|
selector: 'dyn-question',
|
||||||
template: `
|
template: `
|
||||||
<div [ngSwitch]="question.type" [formGroup]="form">
|
<div [ngSwitch]="question.type" [formGroup]="form" [hidden]="type=='delete'">
|
||||||
<label [for]="question.properties.key">{{question.properties.label}}</label>
|
<label [for]="question.properties.key">{{question.properties.label ? question.properties.label + (question.constraints.optional ? '' : ' *') : ''}}</label>
|
||||||
<tag-input *ngSwitchCase="'flag'"
|
<i [hidden]="question.description" class="help circle icon" (mouseover)="onHover(true)" (mouseout)="onHover(false)">Help</i>
|
||||||
[formControlName]="question.properties.key" [id]="question.properties.key"
|
<a [hidden]="!hoverState" class="ui pointing red basic label">{{question.description}}</a><br>
|
||||||
[nullable]="question.constraints.optional" [readonly]="type=='view'"
|
|
||||||
[items]="question.properties.options"
|
<tag-input *ngSwitchCase="'flag'"
|
||||||
(ngModelChange)="change()"></tag-input>
|
[formControlName]="question.properties.key" [id]="question.properties.key"
|
||||||
<hidden-input *ngSwitchCase="'hidden'"
|
[nullable]="question.constraints.optional" [readonly]="type=='view'"
|
||||||
[formControlName]="question.properties.key" [id]="question.properties.key"
|
[items]="question.properties.options"
|
||||||
[nullable]="question.constraints.optional" [readonly]="type=='view'"
|
(ngModelChange)="change()"></tag-input>
|
||||||
(ngModelChange)="change()"></hidden-input>
|
<hidden-input *ngSwitchCase="'hidden'"
|
||||||
<dropdown-input *ngSwitchCase="'dropdown'"
|
[formControlName]="question.properties.key" [id]="question.properties.key"
|
||||||
[formControlName]="question.properties.key" [id]="question.properties.key"
|
[nullable]="question.constraints.optional" [readonly]="type=='view'"
|
||||||
[nullable]="question.constraints.optional" [readonly]="type=='view'"
|
(ngModelChange)="change()"></hidden-input>
|
||||||
[items]="question.properties.options"
|
<dropdown-input *ngSwitchCase="'dropdown'"
|
||||||
(ngModelChange)="change()"></dropdown-input>
|
[formControlName]="question.properties.key" [id]="question.properties.key"
|
||||||
</div>
|
[nullable]="question.constraints.optional" [readonly]="type=='view'"
|
||||||
|
[items]="question.properties.options" [foreign]="question.properties.foreign"
|
||||||
|
(ngModelChange)="change()"></dropdown-input>
|
||||||
|
<textarea-input *ngSwitchCase="'textarea'"
|
||||||
|
[formControlName]="question.properties.key" [id]="question.properties.key"
|
||||||
|
[nullable]="question.constraints.optional" [readonly]="type=='view'"
|
||||||
|
(ngModelChange)="change()"></textarea-input>
|
||||||
|
<textbox-input *ngSwitchCase="'textbox'"
|
||||||
|
[formControlName]="question.properties.key" [id]="question.properties.key"
|
||||||
|
[nullable]="question.constraints.optional" [readonly]="type=='view'"
|
||||||
|
[type]="question.properties.type" [constraints]="question.constraints"
|
||||||
|
[placeholder]="question.properties.placeholder"
|
||||||
|
(ngModelChange)="change()"></textbox-input>
|
||||||
|
<checkbox-input *ngSwitchCase="'checkbox'"
|
||||||
|
[formControlName]="question.properties.key" [id]="question.properties.key"
|
||||||
|
[nullable]="question.constraints.optional" [readonly]="type=='view'"
|
||||||
|
(ngModelChange)="change()"></checkbox-input>
|
||||||
|
|
||||||
|
<ul [hidden]="!errorList.length">
|
||||||
|
<li *ngFor="let error of errorList">{{error}}</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
export class DynQuestionComponent {
|
export class DynQuestionComponent {
|
||||||
@@ -29,8 +50,26 @@ export class DynQuestionComponent {
|
|||||||
@Input() type: "'insert'|'update'|'delete'|'view'";
|
@Input() type: "'insert'|'update'|'delete'|'view'";
|
||||||
@Input() form: FormGroup;
|
@Input() form: FormGroup;
|
||||||
@Output() onChange: EventEmitter<any> = new EventEmitter<any>();
|
@Output() onChange: EventEmitter<any> = new EventEmitter<any>();
|
||||||
|
public hoverState: any = false;
|
||||||
|
private errorList: Array<string> = [];
|
||||||
|
|
||||||
|
onHover(state: boolean) {
|
||||||
|
this.hoverState = state;
|
||||||
|
}
|
||||||
|
|
||||||
change() {
|
change() {
|
||||||
this.onChange.emit();
|
setTimeout(() => {
|
||||||
|
this.errorList = [];
|
||||||
|
let control = this.form.controls[this.question.properties.key];
|
||||||
|
if (control.untouched || control.valid)
|
||||||
|
return null;
|
||||||
|
let errors: ValidationErrors = control.errors;
|
||||||
|
for (let key in errors) {
|
||||||
|
if (errors.hasOwnProperty(key)) {
|
||||||
|
this.errorList.push(errors[key].message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.onChange.emit();
|
||||||
|
}, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
60
src/app/modules/dyn-form/inputs/checkbox-input.component.ts
Normal file
60
src/app/modules/dyn-form/inputs/checkbox-input.component.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import {Component, forwardRef, Input} from '@angular/core';
|
||||||
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, ValidationErrors } from '@angular/forms';
|
||||||
|
import {CustomInputComponent} from "./custom-input.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'checkbox-input',
|
||||||
|
template:
|
||||||
|
`
|
||||||
|
<label *ngIf="!readonly"><input type="radio" [checked]="value===true" (change)="onChange(true)">Yes</label><br *ngIf="!readonly"/>
|
||||||
|
<label *ngIf="!readonly"><input type="radio" [checked]="value===false" (change)="onChange(false)">No</label><br *ngIf="!readonly"/>
|
||||||
|
<label *ngIf="!readonly && nullable"><input type="radio" [checked]="value===null" (change)="onChange(null)">N/A</label>
|
||||||
|
<span *ngIf="readonly">{{value ? "Yes" : (value===false ? "No" : "N/A")}}</span>
|
||||||
|
`,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => CheckboxInputComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: NG_VALIDATORS,
|
||||||
|
useExisting: forwardRef(() => CheckboxInputComponent),
|
||||||
|
multi: true,
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
export class CheckboxInputComponent extends CustomInputComponent {
|
||||||
|
@Input() readonly: boolean = false;
|
||||||
|
@Input() nullable: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
|
// set initial value
|
||||||
|
public writeValue(obj: any): void {
|
||||||
|
if (obj) {
|
||||||
|
this.value = true;
|
||||||
|
} else if (obj===false) {
|
||||||
|
this.value = false;
|
||||||
|
} else {
|
||||||
|
this.value = null;
|
||||||
|
}
|
||||||
|
if (this.value !== obj)
|
||||||
|
setTimeout(() => super.change(true), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the form, returns null when valid else the validation object
|
||||||
|
public validate(c: FormControl): ValidationErrors {
|
||||||
|
if (!this.initComplete)
|
||||||
|
return null;
|
||||||
|
return this.nullable || (this.value) || (this.value===false) ? null : {notNullable: {
|
||||||
|
valid: false,
|
||||||
|
message: "This field is required!"
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// on button click
|
||||||
|
protected onChange(newValue: boolean): void {
|
||||||
|
this.value = newValue;
|
||||||
|
|
||||||
|
super.change();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, forwardRef } from '@angular/core';
|
import {Component, forwardRef} from '@angular/core';
|
||||||
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, ValidationErrors } from '@angular/forms';
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, ValidationErrors } from '@angular/forms';
|
||||||
import {CustomInputComponent} from "./custom-input.component";
|
import {CustomInputComponent} from "./custom-input.component";
|
||||||
|
|
||||||
@@ -6,9 +6,9 @@ import {CustomInputComponent} from "./custom-input.component";
|
|||||||
selector: 'counter-input',
|
selector: 'counter-input',
|
||||||
template:
|
template:
|
||||||
`
|
`
|
||||||
<button (click)="onChange(-1)" (blur)="onBlur()">-</button>
|
<button (click)="onChange(-1)">-</button>
|
||||||
{{value}}
|
{{value}}
|
||||||
<button (click)="onChange(1)" (blur)="onBlur()">+</button>
|
<button (click)="onChange(1)">+</button>
|
||||||
`,
|
`,
|
||||||
providers: [
|
providers: [
|
||||||
{
|
{
|
||||||
@@ -23,6 +23,7 @@ import {CustomInputComponent} from "./custom-input.component";
|
|||||||
}]
|
}]
|
||||||
})
|
})
|
||||||
export class CounterInputComponent extends CustomInputComponent {
|
export class CounterInputComponent extends CustomInputComponent {
|
||||||
|
|
||||||
// set initial value
|
// set initial value
|
||||||
public writeValue(obj: any): void {
|
public writeValue(obj: any): void {
|
||||||
if (obj) {
|
if (obj) {
|
||||||
@@ -33,9 +34,12 @@ export class CounterInputComponent extends CustomInputComponent {
|
|||||||
|
|
||||||
// validates the form, returns null when valid else the validation object
|
// validates the form, returns null when valid else the validation object
|
||||||
public validate(c: FormControl): ValidationErrors {
|
public validate(c: FormControl): ValidationErrors {
|
||||||
|
if (!this.initComplete)
|
||||||
|
return null;
|
||||||
return (!this.parseError) ? null : {
|
return (!this.parseError) ? null : {
|
||||||
numberParseError: {
|
numberParseError: {
|
||||||
valid: false,
|
valid: false,
|
||||||
|
message: "Not a valid number!"
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -47,9 +51,4 @@ export class CounterInputComponent extends CustomInputComponent {
|
|||||||
|
|
||||||
super.change();
|
super.change();
|
||||||
}
|
}
|
||||||
|
|
||||||
// on touched
|
|
||||||
protected onBlur(): void {
|
|
||||||
super.blur();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,10 +1,21 @@
|
|||||||
import {ControlValueAccessor, FormControl, ValidationErrors, Validator} from '@angular/forms';
|
import {ControlValueAccessor, FormControl, ValidationErrors, Validator} from '@angular/forms';
|
||||||
|
import {AfterViewInit} from '@angular/core';
|
||||||
|
|
||||||
export abstract class CustomInputComponent implements ControlValueAccessor, Validator {
|
export abstract class CustomInputComponent implements ControlValueAccessor, Validator, AfterViewInit {
|
||||||
protected value: any;
|
protected value: any;
|
||||||
protected parseError: boolean;
|
protected parseError: boolean;
|
||||||
private propagateChange: (_: any) => void = () => {};
|
private propagateChange: (_: any) => void = () => {};
|
||||||
private propagateTouch: () => void = () => {};
|
private propagateTouch: () => void = () => {};
|
||||||
|
protected initComplete: boolean = false;
|
||||||
|
private isTouched: boolean = false;
|
||||||
|
private initialChangeDone = false;
|
||||||
|
|
||||||
|
public ngAfterViewInit() {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.initComplete = true;
|
||||||
|
this.change();
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
|
||||||
// set initial value
|
// set initial value
|
||||||
public abstract writeValue(obj: any): void;
|
public abstract writeValue(obj: any): void;
|
||||||
@@ -23,12 +34,19 @@ export abstract class CustomInputComponent implements ControlValueAccessor, Vali
|
|||||||
public abstract validate(c: FormControl): ValidationErrors;
|
public abstract validate(c: FormControl): ValidationErrors;
|
||||||
|
|
||||||
// on change
|
// on change
|
||||||
protected change(): void {
|
protected change(noTouch?: boolean): void {
|
||||||
|
if (!noTouch && this.initialChangeDone) {
|
||||||
|
this.touch();
|
||||||
|
}
|
||||||
this.propagateChange(this.value);
|
this.propagateChange(this.value);
|
||||||
|
this.initialChangeDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// on touch
|
// on change
|
||||||
protected blur(): void {
|
protected touch(): void {
|
||||||
this.propagateTouch();
|
if (!this.isTouched) {
|
||||||
|
this.propagateTouch();
|
||||||
|
this.isTouched = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,7 @@
|
|||||||
import {Component, forwardRef, Inject, Input, OnChanges, Pipe, PipeTransform, SimpleChanges} from '@angular/core';
|
import {Component, forwardRef, Input, OnChanges, SimpleChanges} from '@angular/core';
|
||||||
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, ValidationErrors } from '@angular/forms';
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, ValidationErrors } from '@angular/forms';
|
||||||
import {CustomInputComponent} from "./custom-input.component";
|
import {CustomInputComponent} from "./custom-input.component";
|
||||||
import {HelloService} from '../../../services/hello.service';
|
import {ValueService} from '../services/value.service';
|
||||||
|
|
||||||
@Pipe({name: 'keys'})
|
|
||||||
export class KeysPipe implements PipeTransform {
|
|
||||||
transform(value: Object, args:string[]) : any {
|
|
||||||
let keys = [];
|
|
||||||
for (let key in value) {
|
|
||||||
keys.push({key: key, value: value[key]});
|
|
||||||
}
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'dropdown-input',
|
selector: 'dropdown-input',
|
||||||
@@ -33,34 +22,64 @@ export class KeysPipe implements PipeTransform {
|
|||||||
provide: NG_VALIDATORS,
|
provide: NG_VALIDATORS,
|
||||||
useExisting: forwardRef(() => DropdownInputComponent),
|
useExisting: forwardRef(() => DropdownInputComponent),
|
||||||
multi: true,
|
multi: true,
|
||||||
}]
|
},
|
||||||
|
ValueService
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class DropdownInputComponent extends CustomInputComponent implements OnChanges {
|
export class DropdownInputComponent extends CustomInputComponent implements OnChanges {
|
||||||
@Input() readonly: boolean = false;
|
@Input() readonly: boolean = false;
|
||||||
@Input() nullable: boolean = false;
|
@Input() nullable: boolean = false;
|
||||||
@Input() items: Array<{key: string, value: string}> = [];
|
@Input() items: Array<{key: string, value: string}> = [];
|
||||||
|
@Input() foreign: {uri: string, keys: string, values: string, options?: {key: string, value: string}[]};
|
||||||
private listedItems: Array<{key: string, value: string}> = [];
|
private listedItems: Array<{key: string, value: string}> = [];
|
||||||
|
private foreignItems: Array<{key: string, value: string}> = [];
|
||||||
|
private pendingForeign: boolean = false;
|
||||||
private constVal: string;
|
private constVal: string;
|
||||||
|
|
||||||
constructor(private helloService: HelloService) {
|
constructor(private valueService: ValueService) {
|
||||||
super();
|
super();
|
||||||
helloService.sayHello();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// set initial value
|
// set initial value
|
||||||
public writeValue(obj: any): void {
|
public writeValue(obj: any): void {
|
||||||
console.log("init: ", obj);
|
|
||||||
this.value = obj;
|
this.value = obj;
|
||||||
|
console.log("write", this.value);
|
||||||
this.updateConst();
|
this.updateConst();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnChanges(changes: SimpleChanges) {
|
public ngOnChanges(changes: SimpleChanges) {
|
||||||
if (changes['items'] || changes['nullable']) {
|
if (changes['items'] || changes['nullable'] || changes['readonly']) {
|
||||||
if (this.nullable)
|
this.setListedItems();
|
||||||
this.listedItems = [{key: "", value: "N/A"}, {key: null, value: "N/A"}].concat(this.items);
|
|
||||||
else
|
|
||||||
this.listedItems = [].concat(this.items);
|
|
||||||
}
|
}
|
||||||
|
if (changes['foreign'] && this.foreign) {
|
||||||
|
this.pendingForeign = true;
|
||||||
|
this.valueService.getValues(this.foreign.uri, {}, (res, err) => {
|
||||||
|
this.foreignItems = [];
|
||||||
|
for (let item of res as any) {
|
||||||
|
this.foreignItems.push({key: item[this.foreign.keys], value: item[this.foreign.values]});
|
||||||
|
}
|
||||||
|
this.setListedItems();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const val = this.value;
|
||||||
|
this.value = "UNLIKY_VLAUEdrtwe53wrsdsfrsr3drw34rerw3raxdwgxcjlb234r";
|
||||||
|
setTimeout(() => {
|
||||||
|
this.value = val;
|
||||||
|
this.change(false);
|
||||||
|
this.pendingForeign = false;
|
||||||
|
console.log("after timeout", this.value);
|
||||||
|
}, 0);
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private setListedItems() {
|
||||||
|
if (this.nullable)
|
||||||
|
this.listedItems = [{key: "", value: "N/A"}, {key: null, value: "N/A"}].concat(this.items || []).concat(this.foreignItems || []);
|
||||||
|
else
|
||||||
|
this.listedItems = [].concat(this.items || []).concat(this.foreignItems || []);
|
||||||
|
console.log(this.listedItems, this.value);
|
||||||
this.updateConst();
|
this.updateConst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,26 +92,41 @@ export class DropdownInputComponent extends CustomInputComponent implements OnCh
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(this.readonly, this.listedItems, this.value, this.constVal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validates the form, returns null when valid else the validation object
|
// validates the form, returns null when valid else the validation object
|
||||||
public validate(c: FormControl): ValidationErrors {
|
public validate(c: FormControl): ValidationErrors {
|
||||||
|
console.log(this.initComplete, this.value, this.listedItems);
|
||||||
|
if (!this.initComplete)
|
||||||
|
return null;
|
||||||
if (this.value===null) {
|
if (this.value===null) {
|
||||||
return this.nullable ? null : {notNullable: {valid: false}};
|
return this.nullable ? null : {notNullable: {
|
||||||
|
valid: false,
|
||||||
|
message: "This field is required!"
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
for (let i=0; i<this.items.length; i++) {
|
for (let i=0; i<this.listedItems.length; i++) {
|
||||||
if (this.items[i].key==this.value)
|
if (this.listedItems[i].key==this.value)
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return {invalidValue: {valid: false}};
|
return {invalidValue: {
|
||||||
|
valid: false,
|
||||||
|
message: "This value is invalid. Please select another value!"
|
||||||
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
// on button click
|
// on button click
|
||||||
protected onChange(value: any): boolean {
|
protected onChange(value: any): boolean {
|
||||||
this.value = value;
|
this.value = value;
|
||||||
|
console.log("change", this.value);
|
||||||
|
|
||||||
super.change();
|
this.change();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected touch() {
|
||||||
|
console.log("hehehe", this.pendingForeign);
|
||||||
|
if (!this.pendingForeign)
|
||||||
|
super.touch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,7 @@
|
|||||||
import {Component, forwardRef, Input, Pipe, PipeTransform} from '@angular/core';
|
import {Component, forwardRef, Input} from '@angular/core';
|
||||||
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, ValidationErrors } from '@angular/forms';
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, ValidationErrors } from '@angular/forms';
|
||||||
import {CustomInputComponent} from "./custom-input.component";
|
import {CustomInputComponent} from "./custom-input.component";
|
||||||
|
|
||||||
@Pipe({name: 'keys'})
|
|
||||||
export class KeysPipe implements PipeTransform {
|
|
||||||
transform(value: Object, args:string[]) : any {
|
|
||||||
let keys = [];
|
|
||||||
for (let key in value) {
|
|
||||||
keys.push({key: key, value: value[key]});
|
|
||||||
}
|
|
||||||
return keys;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'tag-input',
|
selector: 'tag-input',
|
||||||
template:
|
template:
|
||||||
@@ -58,10 +47,9 @@ export class TagInputComponent extends CustomInputComponent {
|
|||||||
this.value[key] = (this.nullable ? null : false);
|
this.value[key] = (this.nullable ? null : false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("writeValue", isUpdated, this.value);
|
|
||||||
|
|
||||||
if (isUpdated) {
|
if (isUpdated) {
|
||||||
setTimeout(() => super.change(), 0);
|
setTimeout(() => this.change(true), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +74,7 @@ export class TagInputComponent extends CustomInputComponent {
|
|||||||
this.value = Object.assign({}, this.value);
|
this.value = Object.assign({}, this.value);
|
||||||
this.value[key] = value;
|
this.value[key] = value;
|
||||||
|
|
||||||
super.change();
|
this.change();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
54
src/app/modules/dyn-form/inputs/textarea-input.component.ts
Normal file
54
src/app/modules/dyn-form/inputs/textarea-input.component.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import {Component, forwardRef, Input} from '@angular/core';
|
||||||
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, ValidationErrors } from '@angular/forms';
|
||||||
|
import {CustomInputComponent} from "./custom-input.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'textarea-input',
|
||||||
|
template:
|
||||||
|
`
|
||||||
|
<textarea *ngIf="!readonly" (keyup)="onChange($event)">{{value}}</textarea>
|
||||||
|
<span *ngIf="readonly">{{value}}</span>
|
||||||
|
`,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => TextareaInputComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: NG_VALIDATORS,
|
||||||
|
useExisting: forwardRef(() => TextareaInputComponent),
|
||||||
|
multi: true,
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
export class TextareaInputComponent extends CustomInputComponent {
|
||||||
|
@Input() readonly: boolean = false;
|
||||||
|
@Input() nullable: boolean = false;
|
||||||
|
|
||||||
|
|
||||||
|
// set initial value
|
||||||
|
public writeValue(obj: any): void {
|
||||||
|
if (obj) {
|
||||||
|
this.value = obj + '';
|
||||||
|
} else {
|
||||||
|
this.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the form, returns null when valid else the validation object
|
||||||
|
public validate(c: FormControl): ValidationErrors {
|
||||||
|
if (!this.initComplete)
|
||||||
|
return null;
|
||||||
|
return this.nullable || (this.value) ? null : {notNullable: {
|
||||||
|
valid: false,
|
||||||
|
message: "This field is required!"
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
// on button click
|
||||||
|
protected onChange($event: KeyboardEvent): void {
|
||||||
|
this.value = $event.target['value'];
|
||||||
|
|
||||||
|
super.change();
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/app/modules/dyn-form/inputs/textbox-input.component.ts
Normal file
80
src/app/modules/dyn-form/inputs/textbox-input.component.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import {Component, forwardRef, Input} from '@angular/core';
|
||||||
|
import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, ValidationErrors } from '@angular/forms';
|
||||||
|
import {CustomInputComponent} from "./custom-input.component";
|
||||||
|
import {QuestionInterface, QuestionInterfaceCnstr} from '../types/question.interface';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'textbox-input',
|
||||||
|
template:
|
||||||
|
`
|
||||||
|
<input *ngIf="!readonly" (keyup)="onChange($event)" [value]="value" [type]="type" [placeholder]="placeholder"/>
|
||||||
|
<span *ngIf="readonly && value">{{value}}</span>
|
||||||
|
<span *ngIf="readonly && !value"><em>No value specified!</em></span>
|
||||||
|
`,
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: NG_VALUE_ACCESSOR,
|
||||||
|
useExisting: forwardRef(() => TextboxInputComponent),
|
||||||
|
multi: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: NG_VALIDATORS,
|
||||||
|
useExisting: forwardRef(() => TextboxInputComponent),
|
||||||
|
multi: true,
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
export class TextboxInputComponent extends CustomInputComponent {
|
||||||
|
@Input() readonly: boolean = false;
|
||||||
|
@Input() nullable: boolean = false;
|
||||||
|
@Input() constraints: QuestionInterfaceCnstr = null;
|
||||||
|
@Input() type: string;
|
||||||
|
@Input() placeholder: string;
|
||||||
|
|
||||||
|
// set initial value
|
||||||
|
public writeValue(obj: any): void {
|
||||||
|
if (obj) {
|
||||||
|
this.value = obj + '';
|
||||||
|
} else {
|
||||||
|
this.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// validates the form, returns null when valid else the validation object
|
||||||
|
public validate(c: FormControl): ValidationErrors {
|
||||||
|
if (!this.initComplete)
|
||||||
|
return null;
|
||||||
|
if (!this.nullable && !this.value)
|
||||||
|
return {notNullable: {
|
||||||
|
valid: false,
|
||||||
|
message: "This field is required!"
|
||||||
|
}};
|
||||||
|
if (!this.constraints)
|
||||||
|
return null;
|
||||||
|
if (this.constraints.maxLength && this.constraints.maxLength <= this.value.length) {
|
||||||
|
return {tooLong: {
|
||||||
|
valid: false,
|
||||||
|
message: "Length is limited to "+this.constraints.maxLength+" characters."
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
if (this.constraints.maxValue) {
|
||||||
|
let valueInt = parseInt(this.value, 10);
|
||||||
|
if (isNaN(valueInt))
|
||||||
|
return {noNumber: {
|
||||||
|
valid: false,
|
||||||
|
message: "This value is not a number "+this.constraints.maxValue+"."
|
||||||
|
}};
|
||||||
|
if (this.constraints.maxValue <= valueInt) {
|
||||||
|
return {tooLarge: {
|
||||||
|
valid: false,
|
||||||
|
message: "Maximal allowed value is "+this.constraints.maxValue+"."
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// on button click
|
||||||
|
protected onChange($event: KeyboardEvent): void {
|
||||||
|
this.value = $event.target['value'];
|
||||||
|
super.change();
|
||||||
|
}
|
||||||
|
}
|
||||||
109
src/app/modules/dyn-form/services/form.service.ts
Normal file
109
src/app/modules/dyn-form/services/form.service.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {HttpCachedService} from '../../../services/http-cached.service';
|
||||||
|
import {QuestionInterface} from '../types/question.interface';
|
||||||
|
import {ResponseInterface} from '../../../services/response.interface';
|
||||||
|
import { Response } from '@angular/http';
|
||||||
|
import {FormInterface} from '../types/form.interface';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FormService {
|
||||||
|
private readonly PREFIX = "/form";
|
||||||
|
|
||||||
|
constructor(private httpCachedService: HttpCachedService) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public getForm(path: string, cb: (res: FormInterface, err: any) => void) {
|
||||||
|
this.httpCachedService.getJSON(this.PREFIX+path, {}, (data: ResponseInterface, e: Response | any) => {
|
||||||
|
if (e) {
|
||||||
|
cb(null, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.meta.code != 0) {
|
||||||
|
cb(null, data.meta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let form: FormInterface = data.data;
|
||||||
|
form.questions = FormService.orderItems(form.questions);
|
||||||
|
form.filters = FormService.orderItems(form.filters);
|
||||||
|
cb(data.data, e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getQuestions(cb: (res: QuestionInterface[], err: any) => void) {
|
||||||
|
setTimeout(() => cb(FormService.orderItems([
|
||||||
|
{
|
||||||
|
type: 'flag',
|
||||||
|
description: 'This is a help tooltip',
|
||||||
|
properties: {
|
||||||
|
key: 'flags',
|
||||||
|
label: 'Form Type Flags',
|
||||||
|
order: 3,
|
||||||
|
|
||||||
|
// dropdown && flags
|
||||||
|
options: [{key: 'alpha', value: 'A'}, {key: 'beta', value: 'B'}, {key: 'gamma', value: 'C'}, {key: 'delta', value: 'D'}]
|
||||||
|
}, constraints: {
|
||||||
|
optional: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
type: 'dropdown',
|
||||||
|
description: 'Cool dropdown',
|
||||||
|
properties: {
|
||||||
|
key: 'type',
|
||||||
|
label: 'Dropdown',
|
||||||
|
order: 2,
|
||||||
|
methods: ['insert'],
|
||||||
|
|
||||||
|
// dropdown
|
||||||
|
options: [{key: 'hello', value: 'Hallo'}, {key: 'world', value: 'World'}, {key: 'and', value: 'And'}, {key: 'u', value: 'You'}]
|
||||||
|
}, constraints: {
|
||||||
|
optional: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
type: 'hidden',
|
||||||
|
description: 'ID',
|
||||||
|
properties: {
|
||||||
|
key: 'ID',
|
||||||
|
order: 1
|
||||||
|
}, constraints: {
|
||||||
|
optional: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
type: 'textbox',
|
||||||
|
description: 'This is a very long box. Fill it!',
|
||||||
|
properties: {
|
||||||
|
key: 'textarea',
|
||||||
|
label: 'Textareaaaa',
|
||||||
|
order: 4,
|
||||||
|
specialization: 'world',
|
||||||
|
methods: ['insert']
|
||||||
|
}, constraints: {
|
||||||
|
optional: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
type: 'checkbox',
|
||||||
|
description: 'This is a very long box. Fill it!',
|
||||||
|
properties: {
|
||||||
|
key: 'checki',
|
||||||
|
label: 'To be or not to be?',
|
||||||
|
order: 5
|
||||||
|
}, constraints: {
|
||||||
|
optional: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]), null), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static orderItems(list: QuestionInterface[]): QuestionInterface[] {
|
||||||
|
function compare(a: any, b: any) {
|
||||||
|
if (a.properties.order < b.properties.order)
|
||||||
|
return -1;
|
||||||
|
if (a.properties.order > b.properties.order)
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
list.sort(compare);
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
import {HttpCachedService} from '../../../services/http-cached.service';
|
|
||||||
import {QuestionInterface} from '../types/question.interface';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class QuestionService {
|
|
||||||
constructor(private httpCachedService: HttpCachedService) {
|
|
||||||
|
|
||||||
}
|
|
||||||
public getQuestions(cb: (res: QuestionInterface[], err: any) => void) {
|
|
||||||
setTimeout(() => cb([
|
|
||||||
{
|
|
||||||
type: "flag",
|
|
||||||
description: "This is a help tooltip",
|
|
||||||
properties: {
|
|
||||||
key: "flags",
|
|
||||||
label: "Form Type Flags",
|
|
||||||
order: 1,
|
|
||||||
|
|
||||||
// dropdown && flags
|
|
||||||
options: [{key: "alpha", value: "A"}, {key: "beta", value: "B"}]
|
|
||||||
},
|
|
||||||
constraints: {
|
|
||||||
optional: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
], null), 4000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
130
src/app/modules/dyn-form/services/value.service.ts
Normal file
130
src/app/modules/dyn-form/services/value.service.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {HttpCachedService} from '../../../services/http-cached.service';
|
||||||
|
import {Response} from '@angular/http';
|
||||||
|
import {ResponseInterface} from '../../../services/response.interface';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ValueService {
|
||||||
|
private readonly PREFIX = "";
|
||||||
|
|
||||||
|
constructor(private httpCachedService: HttpCachedService) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public getValue(path: string, id: any, cb: (res: Object, err: any) => void) {
|
||||||
|
this.httpCachedService.getJSON(this.PREFIX+path+"/"+id, {}, (data: ResponseInterface, e: Response | any) => {
|
||||||
|
if (e) {
|
||||||
|
cb(null, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.meta.code != 0) {
|
||||||
|
cb(null, data.meta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb(this.unflatObject(data.data[0]), null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getValues(path: string, filters: {[_:string]: any}, cb: (res: Object, err: any) => void) {
|
||||||
|
this.httpCachedService.getJSON(this.PREFIX+path, this.flatObject(filters), (data, e) => {
|
||||||
|
if (e) {
|
||||||
|
cb(null, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.meta.code != 0) {
|
||||||
|
cb(null, data.meta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let ret = data.data;
|
||||||
|
for (let i=0; i<ret.length; i++)
|
||||||
|
ret[i] = this.unflatObject(ret[i]);
|
||||||
|
cb(ret, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public postValues(path: string, values: {[_:string]: any}, cb: (res: Object, err: any) => void) {
|
||||||
|
this.httpCachedService.postJSON(this.PREFIX + path, {}, this.flatObject(values), (data, e) => {
|
||||||
|
if (e) {
|
||||||
|
cb(null, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.meta.code != 0) {
|
||||||
|
cb(null, data.meta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb(this.unflatObject(data.data[0]), null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public putValues(path: string, id: any, values: {[_:string]: any}, cb: (res: Object, err: any) => void) {
|
||||||
|
this.httpCachedService.putJSON(this.PREFIX+path+"/"+id, {}, this.flatObject(values), (data, e) => {
|
||||||
|
if (e) {
|
||||||
|
cb(null, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.meta.code != 0) {
|
||||||
|
cb(null, data.meta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb(this.unflatObject(data.data[0]), null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteValues(path: string, id: any, cb: (res: Object, err: any) => void) {
|
||||||
|
this.httpCachedService.deleteJSON(this.PREFIX+path+"/"+id, {}, (data, e) => {
|
||||||
|
if (e) {
|
||||||
|
cb(null, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data.meta.code != 0) {
|
||||||
|
cb(null, data.meta);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cb(null, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private flatObject(obj: {[_:string]: any}, prefix: string = ''): {[_:string]: any} {
|
||||||
|
let ret: {[_:string]: any} = {};
|
||||||
|
if (Object.prototype.toString.call(obj) == "[object Object]") {
|
||||||
|
if (prefix)
|
||||||
|
prefix = prefix+'.';
|
||||||
|
for (let key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
ret = Object.assign(ret, this.flatObject(obj[key], prefix+key));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret[prefix] = obj;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unflatObject(obj: {[_:string]: any}): {[_:string]: any} {
|
||||||
|
let ret: {[_:string]: any} = {};
|
||||||
|
for (let key in obj) {
|
||||||
|
if (obj.hasOwnProperty(key)) {
|
||||||
|
let strings: string[] = key.split('.');
|
||||||
|
ret = this.addValueToPath(ret, strings, obj[key]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addValueToPath(obj: {[_:string]: any}, path: string[], value: any) {
|
||||||
|
let key = path.shift();
|
||||||
|
if (!path || !path.length)
|
||||||
|
return Object.assign(obj, {[key]: value});
|
||||||
|
if (!obj.hasOwnProperty(key))
|
||||||
|
obj[key] = {};
|
||||||
|
obj[key] = Object.assign(obj[key], this.addValueToPath(obj[key], path, value));
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/app/modules/dyn-form/types/form.interface.ts
Normal file
8
src/app/modules/dyn-form/types/form.interface.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import {QuestionInterface} from './question.interface';
|
||||||
|
export interface FormInterface {
|
||||||
|
description: string,
|
||||||
|
uri: string,
|
||||||
|
name: string,
|
||||||
|
questions: QuestionInterface[],
|
||||||
|
filters: QuestionInterface[]
|
||||||
|
}
|
||||||
12
src/app/modules/dyn-form/types/keys.pipe.ts
Normal file
12
src/app/modules/dyn-form/types/keys.pipe.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import {Pipe, PipeTransform} from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({name: 'keys'})
|
||||||
|
export class KeysPipe implements PipeTransform {
|
||||||
|
transform(value: Object, args:string[]) : any {
|
||||||
|
let keys = [];
|
||||||
|
for (let key in value) {
|
||||||
|
keys.push({key: key, value: value[key]});
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface QuestionInterface {
|
export interface QuestionInterface {
|
||||||
type: "flag"|"textbox"|"textarea"|"hidden"|"dropdown",
|
type: "flag"|"textbox"|"textarea"|"hidden"|"dropdown"|"checkbox",
|
||||||
description: string,
|
description: string,
|
||||||
properties: {
|
properties: {
|
||||||
key: string,
|
key: string,
|
||||||
@@ -7,7 +7,7 @@ export interface QuestionInterface {
|
|||||||
order: number,
|
order: number,
|
||||||
|
|
||||||
methods?: Array<string>,
|
methods?: Array<string>,
|
||||||
spezialization?: string,
|
specialization?: string,
|
||||||
|
|
||||||
// textbox
|
// textbox
|
||||||
type?: string,
|
type?: string,
|
||||||
@@ -19,13 +19,15 @@ export interface QuestionInterface {
|
|||||||
// dropdown & flag
|
// dropdown & flag
|
||||||
options?: {key: string, value: string}[],
|
options?: {key: string, value: string}[],
|
||||||
},
|
},
|
||||||
constraints: {
|
constraints: QuestionInterfaceCnstr
|
||||||
optional: boolean,
|
}
|
||||||
|
|
||||||
// textbox - string
|
export interface QuestionInterfaceCnstr {
|
||||||
maxLength?: number,
|
optional: boolean,
|
||||||
|
|
||||||
// textbox - number
|
// textbox - string
|
||||||
maxValue?: number
|
maxLength?: number,
|
||||||
}
|
|
||||||
|
// textbox - number
|
||||||
|
maxValue?: number
|
||||||
}
|
}
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { Injectable } from '@angular/core';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class HelloService {
|
|
||||||
sayHello(): void {
|
|
||||||
console.log("Hello world from my Service!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,7 @@ import { ResponseInterface } from './response.interface';
|
|||||||
export class HttpCachedService extends HttpService {
|
export class HttpCachedService extends HttpService {
|
||||||
private _stored: any = {}; // {data: ResponseInterface, e: Response}
|
private _stored: any = {}; // {data: ResponseInterface, e: Response}
|
||||||
|
|
||||||
get refreshTime() {
|
static get refreshTime() {
|
||||||
return 3*1000;
|
return 3*1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,45 +16,44 @@ export class HttpCachedService extends HttpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getJSON(uri: string, query: {[propName: string]: any}, dataFunc: (data: ResponseInterface, e: Response | any) => void) {
|
getJSON(uri: string, query: {[propName: string]: any}, dataFunc: (data: ResponseInterface, e: Response | any) => void) {
|
||||||
var hash: string = this._hashRequest("GET", uri, query);
|
const hash: string = this._hashRequest('GET', uri, query);
|
||||||
if (this._stored.hasOwnProperty(hash)) {
|
if (this._stored.hasOwnProperty(hash)) {
|
||||||
if (this._stored[hash] instanceof Array) {
|
if (this._stored[hash] instanceof Array) {
|
||||||
// pending request, add to event emitter
|
// pending request, add to event emitter
|
||||||
this._stored[hash].push(dataFunc);
|
this._stored[hash].push(dataFunc);
|
||||||
} else {
|
} else {
|
||||||
// request alredy stored, send result
|
// request already stored, send result
|
||||||
dataFunc(this._stored[hash].data, this._stored[hash].e);
|
dataFunc(this._stored[hash].data, this._stored[hash].e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// fulfil request, no matching request stored
|
// fulfil request, no matching request stored
|
||||||
this._stored[hash] = [];
|
this._stored[hash] = [];
|
||||||
super.getJSON(uri, query, (data, e) => {
|
super.getJSON(uri, query, (data, e) => {
|
||||||
// request recieved, emit to caller and event subscriber
|
// request received, emit to caller and event subscriber
|
||||||
dataFunc(data, e);
|
dataFunc(data, e);
|
||||||
for (var i = this._stored[hash].length - 1; i >= 0; i--) {
|
for (let i = this._stored[hash].length - 1; i >= 0; i--) {
|
||||||
this._stored[hash][i](data, e);
|
this._stored[hash][i](data, e);
|
||||||
}
|
}
|
||||||
this._stored[hash] = {
|
this._stored[hash] = {
|
||||||
data: data,
|
data: data,
|
||||||
e: e
|
e: e
|
||||||
};
|
};
|
||||||
var that = this;
|
setTimeout(() => {
|
||||||
setTimeout(function() {
|
delete this._stored[hash];
|
||||||
delete that._stored[hash];
|
}, HttpCachedService.refreshTime);
|
||||||
}, this.refreshTime);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _hashRequest(method: string, uri: string, query?: Object): string {
|
private _hashRequest(method: string, uri: string, query?: Object): string {
|
||||||
function objectToString(obj: Object = {}) {
|
function objectToString(obj: Object = {}) {
|
||||||
var keys: string[] = [];
|
const keys: string[] = [];
|
||||||
var result: string = '{';
|
let result: string = '{';
|
||||||
for (var key in obj)
|
for (let key in obj)
|
||||||
if (obj.hasOwnProperty(key))
|
if (obj.hasOwnProperty(key))
|
||||||
keys.push(key);
|
keys.push(key);
|
||||||
keys.sort();
|
keys.sort();
|
||||||
for (var i = keys.length - 1; i >= 0; i--) {
|
for (let i = keys.length - 1; i >= 0; i--) {
|
||||||
result += keys[i]+':"'+obj[keys[i]]+'",'
|
result += keys[i]+':"'+obj[keys[i]]+'",'
|
||||||
}
|
}
|
||||||
result += '}';
|
result += '}';
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { HttpBaseService } from './http-base.service';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class HttpService extends HttpBaseService {
|
export class HttpService extends HttpBaseService {
|
||||||
get httpBase() {
|
get httpBase() {
|
||||||
return "http://ccrruby1-1:3085";
|
return "http://localhost:5000";
|
||||||
//return '/public/mocks';
|
//return '/public/mocks';
|
||||||
}
|
}
|
||||||
get httpSuffix() {
|
get httpSuffix() {
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"mapRoot":"/src/",
|
"mapRoot":"/",
|
||||||
"sourceRoot":"/src/",
|
"sourceRoot":"/src/app",
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"lib": [ "es2015", "dom" ],
|
"lib": [ "es2015", "dom" ],
|
||||||
|
|||||||
Reference in New Issue
Block a user