diff --git a/src/app/app.component.html b/src/app/app.component.html
index 46e4321..54a1708 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,22 +1 @@
-
-
-Value:
-{{ resultCounter }}
-
-
-
-
-
-
-Value:
-{{ resultTag | json }}
-{{ resultTag2 | json }}
-
-
-
\ No newline at end of file
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index b016c4f..798c6aa 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,5 +1,6 @@
-import { Component } from '@angular/core';
+import {Component, Inject} from '@angular/core';
import {QuestionInterface} from './modules/dyn-form/types/question.interface';
+import {QuestionService} from './modules/dyn-form/services/question.service';
@Component({
selector: 'my-app',
@@ -7,26 +8,14 @@ import {QuestionInterface} from './modules/dyn-form/types/question.interface';
})
export class AppComponent {
- public resultCounter = 10;
- public resultTag = {"alpha": true, "beta": false};
- public resultTag2 = {"alpha": true, "beta": false};
+ public formQuestions: QuestionInterface[] = [];
- public formQuestions: QuestionInterface[] = [
- {
- type: "flag",
- description: "This is a help tooltip",
- properties: {
- key: "flags",
- label: "Form Type Flags",
- order: 1,
+ constructor(private questionService: QuestionService) {
+ this.questionService.getQuestions((res, err) => {
+ this.formQuestions = res;
+ });
+ }
- // dropdown && flags
- options: [{key: "alpha", value: "A"}, {key: "beta", value: "B"}]
- }, constraints: {
- optional: true
- }
- }
- ];
submit(value: any) {
console.log("send", value);
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index b67ccab..f255f86 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -2,19 +2,20 @@ import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
-import {CustomInputModule} from "./custom-input.module";
-import { FormsModule, ReactiveFormsModule } from "@angular/forms";
import { DynFormModule } from './modules/dyn-form/dyn-form.module';
+import {HttpCachedService} from './services/http-cached.service';
+import {HttpModule} from '@angular/http';
@NgModule({
imports: [
BrowserModule,
- FormsModule,
- ReactiveFormsModule,
- CustomInputModule,
- DynFormModule
+ DynFormModule,
+ HttpModule
],
declarations: [ AppComponent ],
+ providers: [
+ HttpCachedService
+ ],
bootstrap: [ AppComponent ]
})
export class AppModule { }
diff --git a/src/app/counter-input.component.ts b/src/app/counter-input.component.ts
deleted file mode 100644
index 5fd0d80..0000000
--- a/src/app/counter-input.component.ts
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Component, forwardRef } from '@angular/core';
-import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, ValidationErrors } from '@angular/forms';
-import {CustomInputComponent} from "./custom-input.component";
-
-@Component({
- selector: 'counter-input',
- template:
- `
-
- {{value}}
-
- `,
- providers: [
- {
- provide: NG_VALUE_ACCESSOR,
- useExisting: forwardRef(() => CounterInputComponent),
- multi: true,
- },
- {
- provide: NG_VALIDATORS,
- useExisting: forwardRef(() => CounterInputComponent),
- multi: true,
- }]
-})
-export class CounterInputComponent extends CustomInputComponent {
- // set initial value
- public writeValue(obj: any): void {
- if (obj) {
- this.value = parseInt(obj);
- this.parseError = isNaN(this.value);
- }
- }
-
- // validates the form, returns null when valid else the validation object
- public validate(c: FormControl): ValidationErrors {
- return (!this.parseError) ? null : {
- numberParseError: {
- valid: false,
- },
- };
- }
-
- // on button click
- protected onChange(number: number): void {
- this.value = this.value + number;
- this.parseError = isNaN(this.value);
-
- super.change();
- }
-
- // on touched
- protected onBlur(): void {
- super.blur();
- }
-}
\ No newline at end of file
diff --git a/src/app/custom-input.component.ts b/src/app/custom-input.component.ts
deleted file mode 100644
index 1a2f61b..0000000
--- a/src/app/custom-input.component.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import {ControlValueAccessor, FormControl, ValidationErrors, Validator} from '@angular/forms';
-
-export abstract class CustomInputComponent implements ControlValueAccessor, Validator {
- protected value: any;
- protected parseError: boolean;
- private propagateChange: (_: any) => void = () => {};
- private propagateTouch: () => void = () => {};
-
- // set initial value
- public abstract writeValue(obj: any): void;
-
- // register function to notify on change
- public registerOnChange(fn: (_: any) => void): void {
- this.propagateChange = fn;
- }
-
- // register function to notify on touch
- public registerOnTouched(fn: () => void): void {
- this.propagateTouch = fn;
- }
-
- // validates the form, returns null when valid else the validation object
- public abstract validate(c: FormControl): ValidationErrors;
-
- // on change
- protected change(): void {
- this.propagateChange(this.value);
- }
-
- // on touch
- protected blur(): void {
- this.propagateTouch();
- }
-}
\ No newline at end of file
diff --git a/src/app/custom-input.module.ts b/src/app/custom-input.module.ts
deleted file mode 100644
index b31eaf6..0000000
--- a/src/app/custom-input.module.ts
+++ /dev/null
@@ -1,24 +0,0 @@
-import {FormsModule} from '@angular/forms';
-import { CommonModule } from '@angular/common';
-import { NgModule } from '@angular/core';
-import { CounterInputComponent } from './counter-input.component';
-import {KeysPipe, TagInputComponent} from './tag-input.component';
-
-
-@NgModule({
- imports: [
- CommonModule,
- FormsModule,
- ],
- exports: [
- CounterInputComponent,
- TagInputComponent,
- ],
- declarations: [
- CounterInputComponent,
- TagInputComponent,
- KeysPipe
- ],
- providers: [],
-})
-export class CustomInputModule { }
diff --git a/src/app/modules/dyn-form/dyn-form.module.ts b/src/app/modules/dyn-form/dyn-form.module.ts
index a34af3e..cc79e60 100644
--- a/src/app/modules/dyn-form/dyn-form.module.ts
+++ b/src/app/modules/dyn-form/dyn-form.module.ts
@@ -5,6 +5,7 @@ import { DynFormComponent } from './dyn-form.component';
import {CounterInputComponent} from './inputs/counter-input.component';
import {KeysPipe, TagInputComponent} from './inputs/tag-input.component';
import {DynQuestionComponent} from './dyn-question.component';
+import {QuestionService} from './services/question.service';
@NgModule({
@@ -22,6 +23,8 @@ import {DynQuestionComponent} from './dyn-question.component';
TagInputComponent,
KeysPipe
],
- providers: [],
+ providers: [
+ QuestionService
+ ],
})
export class DynFormModule { }
diff --git a/src/app/modules/dyn-form/services/question.service.ts b/src/app/modules/dyn-form/services/question.service.ts
new file mode 100644
index 0000000..3827249
--- /dev/null
+++ b/src/app/modules/dyn-form/services/question.service.ts
@@ -0,0 +1,29 @@
+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);
+ }
+}
\ No newline at end of file
diff --git a/src/app/services/http-base.service.ts b/src/app/services/http-base.service.ts
new file mode 100644
index 0000000..ffb2cac
--- /dev/null
+++ b/src/app/services/http-base.service.ts
@@ -0,0 +1,152 @@
+import { Http, Response, Headers, RequestOptionsArgs } from '@angular/http';
+import { ReplaySubject } from 'rxjs/ReplaySubject';
+import { Observable } from 'rxjs/Observable';
+import { ResponseInterface } from './response.interface';
+import 'rxjs/add/operator/map';
+import 'rxjs/add/operator/catch';
+import 'rxjs/add/observable/throw';
+
+
+export abstract class HttpBaseService {
+ abstract get httpBase(): string;
+ abstract get httpSuffix(): string;
+
+ errorEmitter: ReplaySubject = new ReplaySubject(1);
+
+ constructor(private _http: Http) {
+
+ }
+
+ getJSON(uri: string, query: {[propName: string]: any}, dataFunc: (data: ResponseInterface, e: Response | any) => void) {
+ var d: ResponseInterface = null;
+ var e: any = null;
+ var that = this;
+ this._doRequest(
+ this._http.get(this._getURI(uri, query), this._getOptions),
+ dataFunc
+ );
+ }
+
+ postJSON(uri: string, query: {[propName: string]: any}, body: {[propName: string]: any}, dataFunc: (data: ResponseInterface, e: Response | any) => void) {
+ var d: ResponseInterface;
+ var e: any;
+ var that = this;
+ this._doRequest(
+ this._http.post(this._getURI(uri, query), this._JSONtoQueryString(body), this._postOptions),
+ dataFunc
+ );
+ }
+
+ putJSON(uri: string, query: {[propName: string]: any}, body: {[propName: string]: any}, dataFunc: (data: ResponseInterface, e: Response | any) => void) {
+ var d: ResponseInterface;
+ var e: any;
+ var that = this;
+ this._doRequest(
+ this._http.put(this._getURI(uri, query), this._JSONtoQueryString(body), this._putOptions),
+ dataFunc
+ );
+ }
+
+ deleteJSON(uri: string, query: {[propName: string]: any}, dataFunc: (data: ResponseInterface, e: Response | any) => void) {
+ var d: ResponseInterface;
+ var e: any;
+ var that = this;
+ this._doRequest(
+ this._http.delete(this._getURI(uri, query), this._deleteOptions),
+ dataFunc
+ );
+ }
+
+ private _doRequest(observe: Observable, dataFunc: (data: ResponseInterface, e: Response | any) => void) {
+ var d: ResponseInterface;
+ var e: any;
+ var that = this;
+
+
+ function _handleError(error: Response | any) {
+ function isJsonParsable(str: any) {
+ try {
+ JSON.parse(str);
+ } catch (e) {
+ return false;
+ }
+ return true;
+ }
+ // In a real world app, we might use a remote logging infrastructure
+ let errMsg: string;
+ let errObj: {status: number, statusText: string, msg: string};
+ if (error instanceof Response) {
+ var err: string;
+ if (isJsonParsable(error.text())) {
+ const body = error.json();
+ err = JSON.stringify(body.meta.text || body.meta || body || '');
+ } else {
+ err = error.text();
+ }
+ errMsg = `${error.status} - ${error.statusText || ''} - ${err}`;
+ errObj = {status: error.status, statusText: error.statusText || '', msg: err};
+ } else {
+ errMsg = error.message ? error.message : error.toString();
+ errObj = {status: -1, statusText: "Server Error", msg: errMsg};
+ }
+ console.error(errMsg);
+ console.error(errObj);
+ that.errorEmitter.next(errObj);
+ return Observable.throw(errMsg);
+ }
+
+ observe
+ .map(this._extractData)
+ .catch(_handleError)
+ .subscribe(
+ data => d = data,
+ error => {e = error; /*_handleError(error);*/ dataFunc(null, e);},
+ () => dataFunc(d, e)
+ );
+ }
+
+ private _getURI(uri: string, query: {[propName: string]: any}): string {
+ var qString: string = this._JSONtoQueryString(query);
+ if (qString)
+ return this.httpBase + uri + this.httpSuffix + '?' + this._JSONtoQueryString(query);
+ else
+ return this.httpBase + uri + this.httpSuffix;
+ }
+
+ private get _getOptions(): RequestOptionsArgs {
+ var headers = new Headers();
+ return {
+ headers: headers,
+ withCredentials: true
+ };
+ }
+
+ private get _postOptions(): RequestOptionsArgs {
+ var headers = new Headers();
+ headers.append('Content-Type', 'application/x-www-form-urlencoded');
+ return {
+ headers: headers,
+ withCredentials: true
+ };
+ }
+
+ private get _putOptions(): RequestOptionsArgs {
+ return this._postOptions;
+ }
+
+ private get _deleteOptions(): RequestOptionsArgs {
+ return this._getOptions;
+ }
+
+ private _JSONtoQueryString(json: {[propName: string]: any}) {
+ return Object.keys(json).map(function(key) {
+ return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]);
+ }).join('&');
+ }
+
+ private _extractData(res: Response): ResponseInterface {
+ var body = res.json();
+ var resBody: ResponseInterface = body as ResponseInterface || null;
+ return resBody;
+ }
+}
\ No newline at end of file
diff --git a/src/app/services/http-cached.service.ts b/src/app/services/http-cached.service.ts
new file mode 100644
index 0000000..503b060
--- /dev/null
+++ b/src/app/services/http-cached.service.ts
@@ -0,0 +1,65 @@
+import { Injectable, Inject } from '@angular/core';
+import { Http, Response } from '@angular/http';
+import { HttpService } from './http.service';
+import { ResponseInterface } from './response.interface';
+
+@Injectable()
+export class HttpCachedService extends HttpService {
+ private _stored: any = {}; // {data: ResponseInterface, e: Response}
+
+ get refreshTime() {
+ return 3*1000;
+ }
+
+ constructor(@Inject(Http) _http: Http) {
+ super(_http);
+ }
+
+ getJSON(uri: string, query: {[propName: string]: any}, dataFunc: (data: ResponseInterface, e: Response | any) => void) {
+ var hash: string = this._hashRequest("GET", uri, query);
+ if (this._stored.hasOwnProperty(hash)) {
+ if (this._stored[hash] instanceof Array) {
+ // pending request, add to event emitter
+ this._stored[hash].push(dataFunc);
+ } else {
+ // request alredy stored, send result
+ dataFunc(this._stored[hash].data, this._stored[hash].e);
+ }
+ } else {
+ // fulfil request, no matching request stored
+ this._stored[hash] = [];
+ super.getJSON(uri, query, (data, e) => {
+ // request recieved, emit to caller and event subscriber
+ dataFunc(data, e);
+ for (var i = this._stored[hash].length - 1; i >= 0; i--) {
+ this._stored[hash][i](data, e);
+ }
+ this._stored[hash] = {
+ data: data,
+ e: e
+ };
+ var that = this;
+ setTimeout(function() {
+ delete that._stored[hash];
+ }, this.refreshTime);
+ });
+ }
+ }
+
+ private _hashRequest(method: string, uri: string, query?: Object): string {
+ function objectToString(obj: Object = {}) {
+ var keys: string[] = [];
+ var result: string = '{';
+ for (var key in obj)
+ if (obj.hasOwnProperty(key))
+ keys.push(key);
+ keys.sort();
+ for (var i = keys.length - 1; i >= 0; i--) {
+ result += keys[i]+':"'+obj[keys[i]]+'",'
+ }
+ result += '}';
+ return result;
+ }
+ return method+" "+uri+" "+objectToString(query);
+ }
+}
\ No newline at end of file
diff --git a/src/app/services/http.service.ts b/src/app/services/http.service.ts
new file mode 100644
index 0000000..c461192
--- /dev/null
+++ b/src/app/services/http.service.ts
@@ -0,0 +1,19 @@
+import { Injectable, Inject } from '@angular/core';
+import { Http } from '@angular/http';
+import { HttpBaseService } from './http-base.service';
+
+@Injectable()
+export class HttpService extends HttpBaseService {
+ get httpBase() {
+ return "http://ccrruby1-1:3085";
+ //return '/public/mocks';
+ }
+ get httpSuffix() {
+ return "";
+ //return '.json';
+ }
+
+ constructor(@Inject(Http) _http: Http) {
+ super(_http);
+ }
+}
\ No newline at end of file
diff --git a/src/app/services/response.interface.ts b/src/app/services/response.interface.ts
new file mode 100644
index 0000000..321bc87
--- /dev/null
+++ b/src/app/services/response.interface.ts
@@ -0,0 +1,9 @@
+export interface ResponseInterface {
+ meta: {
+ code: number,
+ text: string,
+ uri?: string
+ },
+ data?: any,
+ uri?: string
+}
\ No newline at end of file
diff --git a/src/app/tag-input.component.ts b/src/app/tag-input.component.ts
deleted file mode 100644
index 5f7ed4d..0000000
--- a/src/app/tag-input.component.ts
+++ /dev/null
@@ -1,88 +0,0 @@
-import {Component, forwardRef, Input, Pipe, PipeTransform} from '@angular/core';
-import { NG_VALUE_ACCESSOR, NG_VALIDATORS, FormControl, ValidationErrors } from '@angular/forms';
-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({
- selector: 'tag-input',
- template:
- `
-
- {{items[entry.key]}}: {{entry.value}}
-
- `,
- providers: [
- {
- provide: NG_VALUE_ACCESSOR,
- useExisting: forwardRef(() => TagInputComponent),
- multi: true,
- },
- {
- provide: NG_VALIDATORS,
- useExisting: forwardRef(() => TagInputComponent),
- multi: true,
- }]
-})
-export class TagInputComponent extends CustomInputComponent {
- @Input() items: Array<{key: string, value: string}> = [];
- @Input() nullable: boolean = false;
- @Input() id: string = "";
-
- // set initial value
- public writeValue(obj: any): void {
- let isUpdated = false;
- this.value = {};
- if (!obj) {
- obj = {};
- }
- for (let key in this.items) {
- if (this.items.hasOwnProperty(key)) {
- if (obj.hasOwnProperty(key) && (typeof(obj[key]) === "boolean" || (this.nullable && null===obj[key])))
- this.value[key] = obj[key];
- else {
- isUpdated = true;
- this.value[key] = (this.nullable ? null : false);
- }
- }
- }
-
- console.log(this.id, isUpdated, this.value);
- if (isUpdated) {
- super.change();
- }
- }
-
- // validates the form, returns null when valid else the validation object
- public validate(c: FormControl): ValidationErrors {
- return null;
- }
-
- // on button click
- protected onChange(key: string, forward: boolean): boolean {
- let value = this.value[key];
- if (!this.nullable)
- value = !value;
- else if (value===null)
- value = forward;
- else if ((value ? !forward : forward))
- value = null;
- else
- value = !value;
-
- this.value = Object.assign({}, this.value);
- this.value[key] = value;
-
- super.change();
- return false;
- }
-}
\ No newline at end of file