diff --git a/.travis.yml b/.travis.yml index 4e33fe0..91162e2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,5 +31,6 @@ deploy: skip_cleanup: true script: - npx semantic-release + - npm run publish:demo on: branch: master diff --git a/README.md b/README.md index edbcdd2..0ad7ba1 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,13 @@ A [JSON Schema](http://json-schema.org) Form builder for Angular 7+, similar to Note: This project attemtps to take over where its predecesor left off. It's based off of the above project, but rewritten from the ground up. +Note: This project is not ready for consumption. There is still a lot to do to +bring parity with [Angular JSON Schema Form](https://github.com/angular2-json-schema-form) + +## Check out the live demo and play with the examples + +[Check out some examples here.](https://jscharett.github.io/ngx-json-schema-form/) + ### To install from NPM ```shell diff --git a/angular.json b/angular.json index bc1063c..96a3b02 100644 --- a/angular.json +++ b/angular.json @@ -60,9 +60,11 @@ "projects/demo/src/assets" ], "styles": [ - "projects/demo/src/styles.css" + "projects/demo/src/styles.scss" ], - "scripts": [] + "scripts": [ + "node_modules/ace-builds/src-min/ace.js" + ] }, "configurations": { "production": { @@ -116,7 +118,7 @@ "tsConfig": "projects/demo/tsconfig.spec.json", "karmaConfig": "projects/demo/karma.conf.js", "styles": [ - "projects/demo/src/styles.css" + "projects/demo/src/styles.scss" ], "scripts": [], "assets": [ @@ -172,5 +174,10 @@ } } }, - "defaultProject": "ngx-json-schema-form" + "defaultProject": "ngx-json-schema-form", + "schematics": { + "@schematics/angular:component": { + "styleext": "scss" + } + } } diff --git a/package-lock.json b/package-lock.json index 21006d6..76936cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -276,6 +276,23 @@ "tslib": "1.9.3" } }, + "@angular/cdk": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-7.3.7.tgz", + "integrity": "sha512-xbXxhHHKGkVuW6K7pzPmvpJXIwpl0ykBnvA2g+/7Sgy5Pd35wCC+UtHD9RYczDM/mkygNxMQtagyCErwFnDtQA==", + "requires": { + "parse5": "5.1.0", + "tslib": "1.9.3" + }, + "dependencies": { + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "optional": true + } + } + }, "@angular/cli": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-7.2.3.tgz", @@ -475,6 +492,14 @@ "tslib": "1.9.3" } }, + "@angular/flex-layout": { + "version": "7.0.0-beta.24", + "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-7.0.0-beta.24.tgz", + "integrity": "sha512-ll6sK0nLGxqI/f5+z4jbd+pve1QITzgehv2AuGvfSDgIjPMeqUDB5YZqQmIGM/dQRk/vIio5KCW5LQPJWzMMYQ==", + "requires": { + "tslib": "1.9.3" + } + }, "@angular/forms": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-7.2.2.tgz", @@ -483,12 +508,28 @@ "tslib": "1.9.3" } }, + "@angular/http": { + "version": "7.2.15", + "resolved": "https://registry.npmjs.org/@angular/http/-/http-7.2.15.tgz", + "integrity": "sha512-TR7PEdmLWNIre3Zn8lvyb4lSrvPUJhKLystLnp4hBMcWsJqq5iK8S3bnlR4viZ9HMlf7bW7+Hm4SI6aB3tdUtw==", + "requires": { + "tslib": "1.9.3" + } + }, "@angular/language-service": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-7.2.2.tgz", "integrity": "sha512-qkyY5nUT3J/vBvTTZCTFy3isijQseBFGd6gM08o4ycwbTuOOnnC0XUFuv7o8eeu0jd32MGbaK0gikF+OQOCGNQ==", "dev": true }, + "@angular/material": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-7.2.2.tgz", + "integrity": "sha512-HTtDhK5XkDvP6GPg4WTTa0HbeeagTfVRooTfw0TA+IuTAMBhXt5h1yzuGpFyMap8/PUVZN1D04g2CLhBSzoDxg==", + "requires": { + "tslib": "1.9.3" + } + }, "@angular/platform-browser": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-7.2.2.tgz", @@ -1103,6 +1144,11 @@ } } }, + "@types/ace": { + "version": "0.0.36", + "resolved": "https://registry.npmjs.org/@types/ace/-/ace-0.0.36.tgz", + "integrity": "sha512-VBsvNc48suYohki9BSSsRDxwM0RL9Rgi3/GimQ11UMxbbXRjvMSdTvch2aZXfZQ4cyq7PsoHeLvIpsOGXoKNmA==" + }, "@types/estree": { "version": "0.0.39", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", @@ -1417,6 +1463,11 @@ "negotiator": "0.6.1" } }, + "ace-builds": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.4.5.tgz", + "integrity": "sha512-wotVzxv5YClvwOjiuXNyGm4j/CnKoFIoTnnXNmi1nTHjr7hXMMjQeytcnbFua4thaJ5vvpVEDv0utmjqsrp3Jw==" + }, "acorn": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", @@ -2282,6 +2333,11 @@ "widest-line": "2.0.1" } }, + "brace": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", + "integrity": "sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg=" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -7348,6 +7404,15 @@ "integrity": "sha512-HU/YxV4i6GcmiH4duATwAbJQMlE0MsDIR5XmSVxURxKHn3aGAdbY1/ZJFmVRbKtnLwIxxMJD7gYaPsypcbYimg==", "dev": true }, + "jasmine-marbles": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/jasmine-marbles/-/jasmine-marbles-0.6.0.tgz", + "integrity": "sha512-1uzgjEesEeCb+r+v46qn5x326TiGqk5SUZa+A3O+XnMCjG/pGcUOhL9Xsg5L7gLC6RFHyWGTkB5fei4rcvIOiQ==", + "dev": true, + "requires": { + "lodash": "4.17.11" + } + }, "jasmine-spec-reporter": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz", @@ -8692,6 +8757,15 @@ } } }, + "ng2-ace-editor": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/ng2-ace-editor/-/ng2-ace-editor-0.3.9.tgz", + "integrity": "sha512-e8Q4YCirlL/OEiekewmzupG+zV3prYsiYmQnRzQzd0wNgsPjOLOdb0it7cCbzFfIXKGyIIHKTW5584WxPr2LnQ==", + "requires": { + "ace-builds": "1.4.5", + "brace": "0.11.1" + } + }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", diff --git a/package.json b/package.json index 62fbf83..10cc772 100644 --- a/package.json +++ b/package.json @@ -4,9 +4,15 @@ "scripts": { "ng": "ng", "start": "ng serve", - "build": "ng build", + "build": "npm run build:lib && npm run build:demo", + "build:lib": "ng build ngx-json-schema-form", + "build:demo": "ng build demo --prod --base-href=https://jscharett.github.io/ngx-json-schema-form/", "test": "ng test", - "test-ci": "ng test --code-coverage --watch=false", + "test:lib": "ng test ngx-json-schema", + "test:lib-ci": "ng test ngx-json-schema-form --code-coverage --watch=false", + "test:demo": "ng test demo", + "test:demo-ci": "ng test demo --code-coverage --watch=false", + "test-ci": "npm run test:lib-ci && npm run test:demo-ci", "lint": "ng lint", "e2e": "ng e2e", "copy-license": "copy .\\LICENSE .\\dist\\ngx-json-schema-form", @@ -17,22 +23,30 @@ "preversion": "npm run lint && npm run test-ci", "version": "npm run build && git add -A dist", "postversion": "git push && git push --tags && rm -rf build/temp", - "semantic-release": "semantic-release" + "semantic-release": "semantic-release", + "publish:demo": "npx angular-cli-ghpages --dir=./dist/demo" }, "private": false, "dependencies": { "@angular/animations": "~7.2.0", + "@angular/cdk": "^7.2.0", "@angular/common": "~7.2.0", "@angular/compiler": "~7.2.0", "@angular/core": "~7.2.0", + "@angular/flex-layout": "7.0.0-beta.24", "@angular/forms": "~7.2.0", + "@angular/http": "~7.2.0", + "@angular/material": "~7.2.0", "@angular/platform-browser": "~7.2.0", "@angular/platform-browser-dynamic": "~7.2.0", "@angular/router": "~7.2.0", + "@types/ace": "0.0.36", "@types/json-schema": "^7.0.3", "ajv": "^6.10.0", + "brace": "^0.11.0", "core-js": "^2.5.4", "lodash": "^4.17.11", + "ng2-ace-editor": "0.3.9", "rxjs": "~6.5.2", "tslib": "^1.9.0", "zone.js": "~0.9.1" @@ -48,6 +62,7 @@ "@types/node": "~8.9.4", "codelyzer": "~5.1.0", "jasmine-core": "~3.4.0", + "jasmine-marbles": "^0.6.0", "jasmine-spec-reporter": "~4.2.1", "karma": "~4.1.0", "karma-chrome-launcher": "~2.2.0", diff --git a/projects/demo/src/app/app.component.html b/projects/demo/src/app/app.component.html index 5226d57..8fbd53b 100644 --- a/projects/demo/src/app/app.component.html +++ b/projects/demo/src/app/app.component.html @@ -1,20 +1,217 @@ - -
-

- Welcome to {{ title }}! -

- Angular Logo +
+ + NGX Angular JSON Schema Form — Demonstration Playground + +
+ An Angular JSON Schema Form builder + + Choose an example, or create your own, and check out the generated form.

+ Current example: + + + + + + + + + + + + + + + + +
+
+
+
+ + +
+ Input JSON Schema and Form Layout + + (loading form specification...) + +
+
+
+
+ +
+ Generated Form +
+ + + + + + +
{{ jsonFormStatusMessage }}
+
+
+
+
+ +
-

Here are some links to help you start:

- - diff --git a/projects/demo/src/app/app.component.css b/projects/demo/src/app/app.component.scss similarity index 100% rename from projects/demo/src/app/app.component.css rename to projects/demo/src/app/app.component.scss diff --git a/projects/demo/src/app/app.component.spec.ts b/projects/demo/src/app/app.component.spec.ts index 493bc12..a84adba 100644 --- a/projects/demo/src/app/app.component.spec.ts +++ b/projects/demo/src/app/app.component.spec.ts @@ -1,31 +1,87 @@ -import { TestBed } from '@angular/core/testing'; +import { Location } from '@angular/common'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; +import { MatMenuModule } from '@angular/material'; +import { Router } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { cold, getTestScheduler } from 'jasmine-marbles'; + +import examples from '../assets/examples/examples.json'; +import example from '../assets/examples/ngx/simple-array.json'; + import { AppComponent } from './app.component'; +import { routes } from './app.routes'; +import { JsonLoaderService } from './json-loader.service'; describe('AppComponent', () => { - beforeEach(async () => { - return TestBed.configureTestingModule({ - declarations: [ - AppComponent - ] - }).compileComponents(); - }); - - it('should create the app', () => { - const fixture = TestBed.createComponent(AppComponent); - const app: AppComponent = fixture.debugElement.componentInstance; - expect(app).toBeTruthy(); - }); - - it(`should have as title 'demo'`, () => { - const fixture = TestBed.createComponent(AppComponent); - const app: AppComponent = fixture.debugElement.componentInstance; - expect(app.title).toEqual('demo'); - }); - - it('should render title in a h1 tag', () => { - const fixture = TestBed.createComponent(AppComponent); - fixture.detectChanges(); - const compiled: HTMLElement = fixture.debugElement.nativeElement; - expect(compiled.querySelector('h1').textContent).toContain('Welcome to demo!'); - }); + let fixture: ComponentFixture; + let app: AppComponent; + + beforeEach(async () => { + const jsl: JsonLoaderService = jasmine.createSpyObj('JsonLoaderService', { + getExample: cold('a|', {a: JSON.stringify(example)}) + }); + Object.defineProperty(jsl, 'examples', { + enumerable: true, + get: () => cold('a|', {a: examples}) + }); + + return TestBed.configureTestingModule({ + declarations: [ + AppComponent + ], + imports: [ MatMenuModule, RouterTestingModule.withRoutes(routes) ], + providers: [{ + provide: JsonLoaderService, + useValue: jsl + }], + schemas: [ NO_ERRORS_SCHEMA] + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AppComponent); + app = fixture.debugElement.componentInstance; + fixture.detectChanges(); + }); + + it('should create the app', () => { + expect(app).toBeTruthy(); + }); + + it(`should not activate form when empty string`, () => { + expect(app.formActive).toBeFalsy(); + app.generateForm(''); + expect(app.formActive).toBeFalsy(); + }); + + it(`should activate form`, () => { + expect(app.formActive).toBeFalsy(); + app.generateForm('{}'); + expect(app.formActive).toBeTruthy(); + }); + + it(`should not activate form when bad json`, () => { + expect(app.formActive).toBeFalsy(); + app.generateForm('{a:}'); + expect(app.formActive).toBeFalsy(); + expect(app.jsonFormStatusMessage).toContain('JavaScript parser returned'); + }); + + it('should fetch example and generate form', () => { + spyOn(app, 'generateForm'); + app.loadSelectedExample(); + getTestScheduler().flush(); + expect(app.generateForm).toHaveBeenCalledWith(JSON.stringify(example)); + }); + + it('should navigate to the loaded example', fakeAsync(() => { + const router: Router = TestBed.get(Router); + const location: Location = TestBed.get(Location); + router.initialNavigation(); + app.loadSelectedExample('ngx', 'b', 'c', 'd'); + tick(); + expect(location.path()).toBe('/?set=ngx&example=c'); + })); }); diff --git a/projects/demo/src/app/app.component.ts b/projects/demo/src/app/app.component.ts index ba947c9..d3e88ed 100644 --- a/projects/demo/src/app/app.component.ts +++ b/projects/demo/src/app/app.component.ts @@ -1,10 +1,261 @@ -import { Component } from '@angular/core'; +import { animate, state, style, transition, trigger } from '@angular/animations'; +import { Component, OnInit, ViewChild } from '@angular/core'; +import { MatMenuTrigger } from '@angular/material'; +import { ActivatedRoute, Params, Router } from '@angular/router'; + +import { Observable } from 'rxjs'; + +import { JSONSchema7 } from 'json-schema'; + +import { JsonLoaderService } from './json-loader.service'; + +const sets = { + // asf: 'Angular Schema Form:', + // jsf: 'JSONForm:', + ngx: '' + // rsf: 'React Schema Form:' +}; @Component({ - selector: 'app-root', - styleUrls: ['./app.component.css'], - templateUrl: './app.component.html' + animations: [ + trigger('expandSection', [ + state('in', style({ height: '*' })), + transition(':enter', [ + style({ height: 0 }), animate('100ms') + ]), + transition(':leave', [ + style({ height: '*' }), + animate('100ms', style({ height: 0 })) + ]) + ]) + ], + selector: 'app-demo', + styleUrls: ['./app.component.scss'], + templateUrl: './app.component.html' }) -export class AppComponent { - title = 'demo'; +export class AppComponent implements OnInit { + // examples: any = {ngx: {}}; + // languageList: any = ['en', 'fr']; + // languages: any = { + // 'en': 'English', + // 'fr': 'French', + // }; + // frameworkList: any = ['material-design', 'bootstrap-3', 'bootstrap-4', 'no-framework']; + // frameworks: any = { + // 'material-design': 'Material Design', + // 'bootstrap-3': 'Bootstrap 3', + // 'bootstrap-4': 'Bootstrap 4', + // 'no-framework': 'None (plain HTML)', + // }; + example: string; + selectedSet = ''; + selectedSetName = ''; + selectedExample = ''; + selectedExampleName = ''; + // selectedFramework = 'material-design'; + // selectedLanguage = 'en'; + + formActive = false; + jsonFormValid = false; + jsonFormSchema: JSONSchema7; + jsonFormStatusMessage = 'Loading form...'; + // jsonFormObject: any; + // jsonFormOptions: any = { + // addSubmit: true, // Add a submit button if layout does not have one + // debug: false, // Don't show inline debugging information + // loadExternalAssets: true, // Load external css and JavaScript for frameworks + // returnEmptyFields: false, // Don't return values for empty input fields + // setSchemaDefaults: true, // Always use schema defaults for empty fields + // defaultWidgetOptions: { feedback: true }, // Show inline feedback icons + // }; + // liveFormData: any = {}; + // formValidationErrors: any; + // formIsValid = null; + // submittedFormData: any = null; + aceEditorOptions: any = { + autoScrollEditorIntoView: true, + highlightActiveLine: true, + maxLines: 1000, + printMargin: false + }; + @ViewChild(MatMenuTrigger) menuTrigger: MatMenuTrigger; + + public examplesObservable: Observable>; + + constructor( + private readonly route: ActivatedRoute, + private readonly router: Router, + private readonly jsonLoader: JsonLoaderService + ) { } + + ngOnInit() { + this.examplesObservable = this.jsonLoader.examples; + this.selectedSet = 'ngx'; + this.selectedExample = 'simple-array'; + this.selectedExampleName = 'Simple Array'; + this.route.queryParams.subscribe((params: Params) => { + if (params.set) { + this.selectedSet = params.set; + this.selectedSetName = sets[this.selectedSet]; + } + if (params.example) { + this.selectedExample = params.example; + this.jsonLoader.examples.subscribe((examples: Array) => { + this.selectedExampleName = examples + .find((data: any) => data.set === [this.selectedSet]) + .find((data: any) => data.file === this.selectedExample).name; + }); + } + // if (params['framework']) { + // this.selectedFramework = params['framework']; + // } + // if (params['language']) { + // this.selectedLanguage = params['language']; + // } + this.loadSelectedExample(); + }); + } + + trackByFn(index) { + return index; + } + + // onSubmit(data: any) { + // this.submittedFormData = data; + // } + + // get prettySubmittedFormData() { + // return JSON.stringify(this.submittedFormData, null, 2); + // } + + // onChanges(data: any) { + // this.liveFormData = data; + // } + + // get prettyLiveFormData() { + // return JSON.stringify(this.liveFormData, null, 2); + // } + + // isValid(isValid: boolean): void { + // this.formIsValid = isValid; + // } + + // validationErrors(data: any): void { + // this.formValidationErrors = data; + // } + + // get prettyValidationErrors() { + // if (!this.formValidationErrors) { return null; } + // let errorArray = []; + // for (let error of this.formValidationErrors) { + // let message = error.message; + // let dataPathArray = JsonPointer.parse(error.dataPath); + // if (dataPathArray.length) { + // let field = dataPathArray[0]; + // for (let i = 1; i < dataPathArray.length; i++) { + // const key = dataPathArray[i]; + // field += /^\d+$/.test(key) ? `[${key}]` : `.${key}`; + // } + // errorArray.push(`${field}: ${message}`); + // } else { + // errorArray.push(message); + // } + // } + // return errorArray.join('
'); + // } + + loadSelectedExample( + selectedSet: string = this.selectedSet, + selectedSetName: string = this.selectedSetName, + selectedExample: string = this.selectedExample, + selectedExampleName: string = this.selectedExampleName + ): void { + if (this.menuTrigger && this.menuTrigger.menuOpen) { this.menuTrigger.closeMenu(); } + if (selectedExample !== this.selectedExample) { + this.formActive = false; + this.selectedSet = selectedSet; + this.selectedSetName = selectedSetName; + this.selectedExample = selectedExample; + this.selectedExampleName = selectedExampleName; + // https://github.com/declandewet/common-tags#stripindent + this.router.navigateByUrl(`/?set=${selectedSet}&example=${selectedExample}`) + .catch((reason) => { + console.warn(reason); + }); + // &framework=${this.selectedFramework}\ + // &language=${this.selectedLanguage}\ + // this.liveFormData = {}; + // this.submittedFormData = null; + // this.formIsValid = null; + // this.formValidationErrors = null; + } else { + this.jsonLoader.getExample(this.selectedSet, this.selectedExample) + .subscribe((example: string) => { + this.generateForm(example); + }); + } + } + + // loadSelectedLanguage() { + // window.location.href = + // '/?set=' + this.selectedSet + + // '&example=' + this.selectedExample + + // '&framework=' + this.selectedFramework + + // '&language=' + this.selectedLanguage; + // } + + // Display the form entered by the user + // (runs whenever the user changes the jsonform object in the ACE input field) + generateForm(newFormString: string): void { + if (!newFormString) { return; } + this.example = newFormString; + this.jsonFormStatusMessage = 'Loading form...'; + this.formActive = false; + // this.liveFormData = {}; + // this.submittedFormData = null; + + // Most examples should be written in pure JSON, + // but if an example schema includes a function, + // it will be compiled it as Javascript instead + try { + // Parse entered content as JSON + const jsonFormObject = JSON.parse(newFormString); + this.jsonFormSchema = jsonFormObject.schema; + this.jsonFormValid = true; + this.formActive = true; + } catch (jsonError) { + // try { + + // // If entered content is not valid JSON, + // // parse as JavaScript instead to include functions + // let newFormObject: any = null; + // /* tslint:disable */ + // eval('newFormObject = ' + newFormString); + // /* tslint:enable */ + // this.jsonFormObject = newFormObject; + // this.jsonFormValid = true; + // } catch (javascriptError) { + + // // If entered content is not valid JSON or JavaScript, show error + // this.jsonFormValid = false; + this.jsonFormStatusMessage = `Entered content is not currently a valid JSON Form object. + As soon as it is, you will see your form here. So keep typing. :-) + + JavaScript parser returned: + + ${jsonError}`; + // return; + // } + } + } + + // toggleFormOption(option: string) { + // if (option === 'feedback') { + // this.jsonFormOptions.defaultWidgetOptions.feedback = + // !this.jsonFormOptions.defaultWidgetOptions.feedback; + // } else { + // this.jsonFormOptions[option] = !this.jsonFormOptions[option]; + // } + // this.generateForm(this.jsonFormSchema); + // } } diff --git a/projects/demo/src/app/app.module.ts b/projects/demo/src/app/app.module.ts index 394278e..9960015 100644 --- a/projects/demo/src/app/app.module.ts +++ b/projects/demo/src/app/app.module.ts @@ -1,16 +1,38 @@ +import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; +import { FlexLayoutModule } from '@angular/flex-layout'; +import { FormsModule } from '@angular/forms'; +import { + MatButtonModule, MatCardModule, MatCheckboxModule, MatIconModule, + MatMenuModule, MatSelectModule, MatToolbarModule +} from '@angular/material'; import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { RouterModule } from '@angular/router'; + +import { AceEditorModule } from 'ng2-ace-editor'; + +import { JsonSchemaFormModule } from '../../../ngx-json-schema-form/src/public-api'; import { AppComponent } from './app.component'; +import { routes } from './app.routes'; +import { RootComponent } from './root.component'; @NgModule({ - bootstrap: [AppComponent], - declarations: [ - AppComponent - ], - imports: [ - BrowserModule - ], - providers: [] + bootstrap: [ RootComponent ], + declarations: [ + AppComponent, + RootComponent + ], + imports: [ + BrowserModule, BrowserAnimationsModule, FormsModule, HttpClientModule, FlexLayoutModule, + MatButtonModule, MatCardModule, MatCheckboxModule, + MatIconModule, MatMenuModule, MatSelectModule, MatToolbarModule, + RouterModule.forRoot(routes), + + AceEditorModule, + + JsonSchemaFormModule + ] }) export class AppModule { } diff --git a/projects/demo/src/app/app.routes.ts b/projects/demo/src/app/app.routes.ts new file mode 100644 index 0000000..7143d57 --- /dev/null +++ b/projects/demo/src/app/app.routes.ts @@ -0,0 +1,8 @@ +import { Route } from '@angular/router'; + +import { AppComponent } from './app.component'; + +export const routes: Array = [ + { path: '', component: AppComponent }, + { path: '**', component: AppComponent } +]; diff --git a/projects/demo/src/app/json-loader.service.spec.ts b/projects/demo/src/app/json-loader.service.spec.ts new file mode 100644 index 0000000..b1948f1 --- /dev/null +++ b/projects/demo/src/app/json-loader.service.spec.ts @@ -0,0 +1,32 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TestBed } from '@angular/core/testing'; + +import { cold } from 'jasmine-marbles'; + +import { JsonLoaderService } from './json-loader.service'; + +describe('JsonLoaderService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ HttpClientTestingModule ], + providers: [ + JsonLoaderService + ] + }); + }); + + it('should be created', () => { + const service: JsonLoaderService = TestBed.get(JsonLoaderService); + expect(service).toBeTruthy(); + }); + + it('should have observable examples', () => { + const service: JsonLoaderService = TestBed.get(JsonLoaderService); + expect(service.examples).toBeObservable(cold('-')); + }); + + it('should have observable example', () => { + const service: JsonLoaderService = TestBed.get(JsonLoaderService); + expect(service.getExample('ngx', 'basic')).toBeObservable(cold('-')); + }); +}); diff --git a/projects/demo/src/app/json-loader.service.ts b/projects/demo/src/app/json-loader.service.ts new file mode 100644 index 0000000..c830c50 --- /dev/null +++ b/projects/demo/src/app/json-loader.service.ts @@ -0,0 +1,24 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; + +import { Observable } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class JsonLoaderService { + private readonly examplesURL = 'assets/examples/examples.json'; + + constructor(private readonly httpClient: HttpClient) {} + + get examples(): Observable { + return this.httpClient.get(this.examplesURL); + } + + getExample(set: string, example: string): Observable { + const exampleURL = `assets/examples/${set}/${example}.json`; + + return this.httpClient.get(exampleURL, { responseType: 'text' }); + } + +} diff --git a/projects/demo/src/app/root.component.ts b/projects/demo/src/app/root.component.ts new file mode 100644 index 0000000..c23b495 --- /dev/null +++ b/projects/demo/src/app/root.component.ts @@ -0,0 +1,7 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-root', + template: `` +}) +export class RootComponent { } diff --git a/projects/demo/src/assets/examples/examples.json b/projects/demo/src/assets/examples/examples.json new file mode 100644 index 0000000..c559fa3 --- /dev/null +++ b/projects/demo/src/assets/examples/examples.json @@ -0,0 +1,13 @@ +[ + { + "set": "ngx", + "label": "NGX Angular JSON Schema Form:", + "name": "NGX Angular JSON Schema Form examples", + "examples": [ + { + "name": "Simple Array", + "file": "simple-array" + } + ] + } +] diff --git a/projects/demo/src/assets/examples/ngx/simple-array.json b/projects/demo/src/assets/examples/ngx/simple-array.json new file mode 100644 index 0000000..b072993 --- /dev/null +++ b/projects/demo/src/assets/examples/ngx/simple-array.json @@ -0,0 +1,18 @@ +{ + "schema": { + "type": "object", + "properties": { + "items": { + "type": "array", + "items": { + "type": "string", + "title": "Item", + "default": "New Item" + } + } + } + }, + "data": { + "items": [ "Item 1", "Item 2", "Item 3", "Item 4" ] + } +} diff --git a/projects/demo/src/index.html b/projects/demo/src/index.html index 77e5ff2..befe662 100644 --- a/projects/demo/src/index.html +++ b/projects/demo/src/index.html @@ -1,14 +1,16 @@ - - Demo - + + NGX Angular JSON Schema Form Demo + - - + + + + - + diff --git a/projects/demo/src/main.ts b/projects/demo/src/main.ts index 3192888..2bc3b38 100644 --- a/projects/demo/src/main.ts +++ b/projects/demo/src/main.ts @@ -4,6 +4,8 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; +import 'brace/mode/json'; + if (environment.production) { enableProdMode(); } diff --git a/projects/demo/src/styles.css b/projects/demo/src/styles.css deleted file mode 100644 index 90d4ee0..0000000 --- a/projects/demo/src/styles.css +++ /dev/null @@ -1 +0,0 @@ -/* You can add global styles to this file, and also import other style files */ diff --git a/projects/demo/src/styles.scss b/projects/demo/src/styles.scss new file mode 100644 index 0000000..eb3d55e --- /dev/null +++ b/projects/demo/src/styles.scss @@ -0,0 +1,74 @@ +// * { border: 1px solid red !important; } +@import '~@angular/material/theming'; +@include mat-core(); +$demo-app-primary: mat-palette($mat-blue); +$demo-app-accent: mat-palette($mat-amber, A200, A100, A400); +$demo-app-warn: mat-palette($mat-red); +$demo-app-theme: mat-light-theme($demo-app-primary, $demo-app-accent, $demo-app-warn); +@include angular-material-theme($demo-app-theme); + +$font-family: 'Roboto', 'Noto', 'Helvetica Neue', sans-serif; +$row-height: 56px; + +mat-toolbar { + &.mat-medium { + min-height: $row-height; + mat-toolbar-row { height: $row-height; } + } +} + +body { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + background-color: rgb(250, 250, 250) !important; + display: flex; + flex: 1 1 auto; + flex-direction: column; + font-family: $font-family; + height: 100%; + margin: 0; + padding: 0; +} + +.demo-page-header { + background-color: mat-color($demo-app-primary, lighter); + margin-bottom: 12px; + .header-content { + font-family: $font-family; + line-height: 1.4em; + padding: 12px; + .menu-label { + margin-right: 12px; + font-weight: bold; + } + } +} + +.ace_active-line { background: none !important; } + +.data-good, .data-bad { + border-radius: 3px; + padding: 6px; + border: 1px solid #ccc !important; +} + +.avoidwrap { display:inline-block; } + +.data-good { background-color: #dfd; } + +.data-bad { background-color: #fcc; } + +.check-row { margin-top: 8px; } + +.cdk-overlay-container .cdk-overlay-pane .mat-menu-panel { max-width: 560px; } + +.debug { border: 1px solid red !important; } + +.mat-input-container.mat-form-field { width: 100%; } + +details { + summary { + font-weight: bold; + margin: 0 0 1.33em; + } +} diff --git a/projects/demo/src/test.ts b/projects/demo/src/test.ts index abc8165..279813f 100644 --- a/projects/demo/src/test.ts +++ b/projects/demo/src/test.ts @@ -1,13 +1,13 @@ // This file is required by karma.conf.js and loads recursively all the .spec and framework files +import 'zone.js/dist/zone-testing'; + import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; -import 'zone.js/dist/zone-testing'; - declare const require: any; // First, initialize the Angular testing environment. diff --git a/projects/demo/tsconfig.spec.json b/projects/demo/tsconfig.spec.json index a809b0a..9ca5dde 100644 --- a/projects/demo/tsconfig.spec.json +++ b/projects/demo/tsconfig.spec.json @@ -5,7 +5,8 @@ "types": [ "jasmine", "node" - ] + ], + "resolveJsonModule": true }, "files": [ "src/test.ts", diff --git a/projects/ngx-json-schema-form/src/lib/widget-library/button/button.component.css b/projects/ngx-json-schema-form/src/lib/widget-library/button/button.component.scss similarity index 100% rename from projects/ngx-json-schema-form/src/lib/widget-library/button/button.component.css rename to projects/ngx-json-schema-form/src/lib/widget-library/button/button.component.scss diff --git a/projects/ngx-json-schema-form/src/lib/widget-library/button/button.component.ts b/projects/ngx-json-schema-form/src/lib/widget-library/button/button.component.ts index f29b540..750b87d 100644 --- a/projects/ngx-json-schema-form/src/lib/widget-library/button/button.component.ts +++ b/projects/ngx-json-schema-form/src/lib/widget-library/button/button.component.ts @@ -6,7 +6,7 @@ import { Widget } from '../widget'; @Component({ selector: 'jsf-button', - styleUrls: ['./button.component.css'], + styleUrls: ['./button.component.scss'], templateUrl: './button.component.html' }) export class ButtonComponent extends Widget { diff --git a/tslint.json b/tslint.json index 533555b..8a7ebe4 100644 --- a/tslint.json +++ b/tslint.json @@ -25,7 +25,7 @@ ], "no-any": false, "no-empty-interface": true, - "no-import-side-effect": [true, {"ignore-module": "(\\.html|\\.css)$|^(zone.js|core.js)"}], + "no-import-side-effect": [true, {"ignore-module": "(\\.html|\\.css)$|^(zone.js|core.js|brace)"}], "no-inferrable-types": true, "no-internal-module": true, "no-magic-numbers": true, @@ -136,7 +136,7 @@ "spaces", 4 ], - "linebreak-style": [true, "LF"], + "linebreak-style": [false, "LF"], "max-classes-per-file": [true, 1], "max-file-line-count": [true, 400], "max-line-length": [ @@ -202,7 +202,7 @@ ], "one-variable-per-declaration": [true, "ignore-for-loop"], "ordered-imports": true, - "prefer-function-over-method": true, + "prefer-function-over-method": [true, "allow-public"], "prefer-method-signature": true, "prefer-switch": [true, {"min-cases": 2}], "prefer-template": true,