├── src ├── assets │ └── .gitkeep ├── app │ ├── app.component.scss │ ├── form-builder │ │ ├── form-builder.component.scss │ │ ├── form-builder.component.html │ │ ├── form-builder.component.spec.ts │ │ ├── form-builder.component.ts │ │ └── options.ts │ ├── form-renderer │ │ ├── form-renderer.component.scss │ │ ├── form-renderer.component.html │ │ ├── form-renderer.component.ts │ │ └── form-renderer.component.spec.ts │ ├── app-routing.module.ts │ ├── app.module.ts │ ├── app.component.spec.ts │ ├── app.component.ts │ └── app.component.html ├── favicon.ico ├── styles.scss ├── main.ts └── index.html ├── screenshots ├── formBuilder.png ├── formBuilderBlank.png ├── formRendererBlank.png ├── formRendererFilled.png └── formRendererReadOnly.png ├── .vscode ├── extensions.json ├── launch.json └── tasks.json ├── tsconfig.app.json ├── tsconfig.spec.json ├── .editorconfig ├── .gitignore ├── tsconfig.json ├── package.json ├── angular.json └── README.md /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/form-builder/form-builder.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/form-renderer/form-renderer.component.scss: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniruddhaSadhukhan/dynamic-forms/HEAD/src/favicon.ico -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /screenshots/formBuilder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniruddhaSadhukhan/dynamic-forms/HEAD/screenshots/formBuilder.png -------------------------------------------------------------------------------- /screenshots/formBuilderBlank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniruddhaSadhukhan/dynamic-forms/HEAD/screenshots/formBuilderBlank.png -------------------------------------------------------------------------------- /screenshots/formRendererBlank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniruddhaSadhukhan/dynamic-forms/HEAD/screenshots/formRendererBlank.png -------------------------------------------------------------------------------- /screenshots/formRendererFilled.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniruddhaSadhukhan/dynamic-forms/HEAD/screenshots/formRendererFilled.png -------------------------------------------------------------------------------- /screenshots/formRendererReadOnly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AniruddhaSadhukhan/dynamic-forms/HEAD/screenshots/formRendererReadOnly.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846 3 | "recommendations": ["angular.ng-template"] 4 | } 5 | -------------------------------------------------------------------------------- /src/app/form-renderer/form-renderer.component.html: -------------------------------------------------------------------------------- 1 |
2 | 8 |
9 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app/app.module'; 4 | 5 | 6 | platformBrowserDynamic().bootstrapModule(AppModule) 7 | .catch(err => console.error(err)); 8 | -------------------------------------------------------------------------------- /src/app/app-routing.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { RouterModule, Routes } from '@angular/router'; 3 | 4 | const routes: Routes = []; 5 | 6 | @NgModule({ 7 | imports: [RouterModule.forRoot(routes)], 8 | exports: [RouterModule] 9 | }) 10 | export class AppRoutingModule { } 11 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/app", 6 | "types": [] 7 | }, 8 | "files": [ 9 | "src/main.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "extends": "./tsconfig.json", 4 | "compilerOptions": { 5 | "outDir": "./out-tsc/spec", 6 | "types": [ 7 | "jasmine" 8 | ] 9 | }, 10 | "include": [ 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DynamicForms 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/app/form-builder/form-builder.component.html: -------------------------------------------------------------------------------- 1 |
2 | 7 |
8 | 11 |
12 |
13 | -------------------------------------------------------------------------------- /src/app/form-renderer/form-renderer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'dynamic-form-renderer', 5 | templateUrl: './form-renderer.component.html', 6 | styleUrls: ['./form-renderer.component.scss'], 7 | }) 8 | export class FormRendererComponent { 9 | @Input() form; 10 | @Input() submission; 11 | @Input() readOnly; 12 | @Output() submit: EventEmitter = new EventEmitter(); 13 | 14 | onSubmit(event) { 15 | this.submit.emit(event); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "name": "ng serve", 7 | "type": "chrome", 8 | "request": "launch", 9 | "preLaunchTask": "npm: start", 10 | "url": "http://localhost:4200/" 11 | }, 12 | { 13 | "name": "ng test", 14 | "type": "chrome", 15 | "request": "launch", 16 | "preLaunchTask": "npm: test", 17 | "url": "http://localhost:9876/debug.html" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /src/app/form-builder/form-builder.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FormBuilderComponent } from './form-builder.component'; 4 | 5 | describe('FormBuilderComponent', () => { 6 | let component: FormBuilderComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [FormBuilderComponent] 12 | }); 13 | fixture = TestBed.createComponent(FormBuilderComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/app/form-renderer/form-renderer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { FormRendererComponent } from './form-renderer.component'; 4 | 5 | describe('FormRendererComponent', () => { 6 | let component: FormRendererComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [FormRendererComponent] 12 | }); 13 | fixture = TestBed.createComponent(FormRendererComponent); 14 | component = fixture.componentInstance; 15 | fixture.detectChanges(); 16 | }); 17 | 18 | it('should create', () => { 19 | expect(component).toBeTruthy(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /bazel-out 8 | 9 | # Node 10 | /node_modules 11 | npm-debug.log 12 | yarn-error.log 13 | 14 | # IDEs and editors 15 | .idea/ 16 | .project 17 | .classpath 18 | .c9/ 19 | *.launch 20 | .settings/ 21 | *.sublime-workspace 22 | 23 | # Visual Studio Code 24 | .vscode/* 25 | !.vscode/settings.json 26 | !.vscode/tasks.json 27 | !.vscode/launch.json 28 | !.vscode/extensions.json 29 | .history/* 30 | 31 | # Miscellaneous 32 | /.angular/cache 33 | .sass-cache/ 34 | /connect.lock 35 | /coverage 36 | /libpeerconnection.log 37 | testem.log 38 | /typings 39 | 40 | # System files 41 | .DS_Store 42 | Thumbs.db 43 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | 4 | import { AppRoutingModule } from './app-routing.module'; 5 | import { AppComponent } from './app.component'; 6 | import { FormBuilderComponent } from './form-builder/form-builder.component'; 7 | import { FormRendererComponent } from './form-renderer/form-renderer.component'; 8 | import { FormioModule } from '@formio/angular'; 9 | 10 | @NgModule({ 11 | declarations: [ 12 | AppComponent, 13 | FormBuilderComponent, 14 | FormRendererComponent 15 | ], 16 | imports: [ 17 | BrowserModule, 18 | AppRoutingModule, 19 | FormioModule 20 | ], 21 | providers: [], 22 | bootstrap: [AppComponent] 23 | }) 24 | export class AppModule { } 25 | -------------------------------------------------------------------------------- /src/app/form-builder/form-builder.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; 2 | import { options } from './options'; 3 | 4 | @Component({ 5 | selector: 'dynamic-form-builder', 6 | templateUrl: './form-builder.component.html', 7 | styleUrls: ['./form-builder.component.scss'], 8 | }) 9 | export class FormBuilderComponent implements OnInit { 10 | @Input() initialForm: any; 11 | @Output() save: EventEmitter = new EventEmitter(); 12 | options: any; 13 | 14 | form: Object = { components: [] }; 15 | 16 | generatedForm: Object; 17 | 18 | constructor() { 19 | this.options = options; 20 | } 21 | 22 | ngOnInit(): void { 23 | if (this.initialForm?.components?.length) { 24 | this.form = this.initialForm; 25 | } 26 | this.generatedForm = this.initialForm; 27 | } 28 | 29 | onChange(event) { 30 | this.generatedForm = event.form; 31 | } 32 | 33 | saveForm() { 34 | this.save.emit(this.generatedForm); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558 3 | "version": "2.0.0", 4 | "tasks": [ 5 | { 6 | "type": "npm", 7 | "script": "start", 8 | "isBackground": true, 9 | "problemMatcher": { 10 | "owner": "typescript", 11 | "pattern": "$tsc", 12 | "background": { 13 | "activeOnStart": true, 14 | "beginsPattern": { 15 | "regexp": "(.*?)" 16 | }, 17 | "endsPattern": { 18 | "regexp": "bundle generation complete" 19 | } 20 | } 21 | } 22 | }, 23 | { 24 | "type": "npm", 25 | "script": "test", 26 | "isBackground": true, 27 | "problemMatcher": { 28 | "owner": "typescript", 29 | "pattern": "$tsc", 30 | "background": { 31 | "activeOnStart": true, 32 | "beginsPattern": { 33 | "regexp": "(.*?)" 34 | }, 35 | "endsPattern": { 36 | "regexp": "bundle generation complete" 37 | } 38 | } 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | /* To learn more about this file see: https://angular.io/config/tsconfig. */ 2 | { 3 | "compileOnSave": false, 4 | "compilerOptions": { 5 | "baseUrl": "./", 6 | "outDir": "./dist/out-tsc", 7 | "forceConsistentCasingInFileNames": true, 8 | "strict": true, 9 | "noImplicitOverride": true, 10 | "noPropertyAccessFromIndexSignature": true, 11 | "noImplicitReturns": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "sourceMap": true, 14 | "declaration": false, 15 | "downlevelIteration": true, 16 | "experimentalDecorators": true, 17 | "moduleResolution": "node", 18 | "importHelpers": true, 19 | "useDefineForClassFields": false, 20 | "noImplicitAny": false, 21 | "strictPropertyInitialization": false, 22 | "strictNullChecks": false, 23 | "target": "ESNext", 24 | "module": "ESNext", 25 | "lib": ["ESNext", "dom"] 26 | }, 27 | "angularCompilerOptions": { 28 | "enableI18nLegacyMessageIdFormat": false, 29 | "strictInjectionParameters": true, 30 | "strictInputAccessModifiers": true, 31 | "strictTemplates": true 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | 5 | describe('AppComponent', () => { 6 | beforeEach(() => TestBed.configureTestingModule({ 7 | imports: [RouterTestingModule], 8 | declarations: [AppComponent] 9 | })); 10 | 11 | it('should create the app', () => { 12 | const fixture = TestBed.createComponent(AppComponent); 13 | const app = fixture.componentInstance; 14 | expect(app).toBeTruthy(); 15 | }); 16 | 17 | it(`should have as title 'dynamic-forms'`, () => { 18 | const fixture = TestBed.createComponent(AppComponent); 19 | const app = fixture.componentInstance; 20 | expect(app.title).toEqual('dynamic-forms'); 21 | }); 22 | 23 | it('should render title', () => { 24 | const fixture = TestBed.createComponent(AppComponent); 25 | fixture.detectChanges(); 26 | const compiled = fixture.nativeElement as HTMLElement; 27 | expect(compiled.querySelector('.content span')?.textContent).toContain('dynamic-forms app is running!'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'], 7 | }) 8 | export class AppComponent implements OnInit { 9 | pages = ['Form Builder', 'Form Renderer', 'Form Reader']; 10 | currentPage = 'Form Builder'; 11 | form: Object; 12 | submission: Object; 13 | initialForm: Object; 14 | currentSubmission = {}; 15 | 16 | changePage(page: string) { 17 | this.currentPage = page; 18 | } 19 | 20 | ngOnInit(): void { 21 | let jsonForm = localStorage.getItem('jsonForm'); 22 | let submissionStr = localStorage.getItem('jsonFormSubmission'); 23 | if (jsonForm) { 24 | this.form = JSON.parse(jsonForm); 25 | } 26 | if (submissionStr) { 27 | this.submission = JSON.parse(submissionStr); 28 | } 29 | this.initialForm = JSON.parse(JSON.stringify(this.form)); 30 | } 31 | 32 | onFormSave(event) { 33 | this.form = event; 34 | localStorage.setItem('jsonForm', JSON.stringify(event)); 35 | } 36 | 37 | onSubmission(event) { 38 | this.submission = event; 39 | this.currentSubmission = event; 40 | localStorage.setItem('jsonFormSubmission', JSON.stringify(event)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dynamic-forms", 3 | "version": "0.0.0", 4 | "scripts": { 5 | "ng": "ng", 6 | "update": "npm update --legacy-peer-deps", 7 | "serve": "ng serve", 8 | "build": "ng build", 9 | "deploy": "surge --project ./dist/dynamic-forms --domain ani-dynamic-forms-demo.surge.sh", 10 | "test": "ng test" 11 | }, 12 | "private": true, 13 | "dependencies": { 14 | "@angular/animations": "^16.1.0", 15 | "@angular/common": "^16.1.0", 16 | "@angular/compiler": "^16.1.0", 17 | "@angular/core": "^16.1.0", 18 | "@angular/elements": "^16.2.2", 19 | "@angular/forms": "^16.1.0", 20 | "@angular/platform-browser": "^16.1.0", 21 | "@angular/platform-browser-dynamic": "^16.1.0", 22 | "@angular/router": "^16.1.0", 23 | "@formio/angular": "^6.0.0-rc.5", 24 | "@formio/js": "^5.0.0-rc.27", 25 | "font-awesome": "^4.7.0", 26 | "rxjs": "~7.8.0", 27 | "tslib": "^2.3.0", 28 | "zone.js": "~0.13.0" 29 | }, 30 | "devDependencies": { 31 | "@angular-devkit/build-angular": "^16.1.8", 32 | "@angular/cli": "~16.2.0", 33 | "@angular/compiler-cli": "^16.1.0", 34 | "@types/jasmine": "~4.3.0", 35 | "jasmine-core": "~4.6.0", 36 | "karma": "~6.4.0", 37 | "karma-chrome-launcher": "~3.2.0", 38 | "karma-coverage": "~2.2.0", 39 | "karma-jasmine": "~5.1.0", 40 | "karma-jasmine-html-reporter": "~2.1.0", 41 | "typescript": "~5.1.3" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "dynamic-forms": { 7 | "projectType": "application", 8 | "schematics": { 9 | "@schematics/angular:component": { 10 | "style": "scss" 11 | } 12 | }, 13 | "root": "", 14 | "sourceRoot": "src", 15 | "prefix": "app", 16 | "architect": { 17 | "build": { 18 | "builder": "@angular-devkit/build-angular:browser", 19 | "options": { 20 | "outputPath": "dist/dynamic-forms", 21 | "index": "src/index.html", 22 | "main": "src/main.ts", 23 | "polyfills": ["zone.js"], 24 | "tsConfig": "tsconfig.app.json", 25 | "inlineStyleLanguage": "scss", 26 | "assets": ["src/favicon.ico", "src/assets"], 27 | "styles": [ 28 | "src/styles.scss", 29 | "font-awesome/css/font-awesome.min.css", 30 | "bootstrap/dist/css/bootstrap.min.css" 31 | ], 32 | "stylePreprocessorOptions": { 33 | "includePaths": ["./node_modules"] 34 | }, 35 | "scripts": ["node_modules/bootstrap/dist/js/bootstrap.min.js"] 36 | }, 37 | "configurations": { 38 | "production": { 39 | "budgets": [ 40 | { 41 | "type": "initial", 42 | "maximumWarning": "3mb", 43 | "maximumError": "5mb" 44 | }, 45 | { 46 | "type": "anyComponentStyle", 47 | "maximumWarning": "2kb", 48 | "maximumError": "4kb" 49 | } 50 | ], 51 | "outputHashing": "all" 52 | }, 53 | "development": { 54 | "buildOptimizer": false, 55 | "optimization": false, 56 | "vendorChunk": true, 57 | "extractLicenses": false, 58 | "sourceMap": true, 59 | "namedChunks": true 60 | } 61 | }, 62 | "defaultConfiguration": "production" 63 | }, 64 | "serve": { 65 | "builder": "@angular-devkit/build-angular:dev-server", 66 | "configurations": { 67 | "production": { 68 | "browserTarget": "dynamic-forms:build:production" 69 | }, 70 | "development": { 71 | "browserTarget": "dynamic-forms:build:development" 72 | } 73 | }, 74 | "defaultConfiguration": "development" 75 | }, 76 | "extract-i18n": { 77 | "builder": "@angular-devkit/build-angular:extract-i18n", 78 | "options": { 79 | "browserTarget": "dynamic-forms:build" 80 | } 81 | }, 82 | "test": { 83 | "builder": "@angular-devkit/build-angular:karma", 84 | "options": { 85 | "polyfills": ["zone.js", "zone.js/testing"], 86 | "tsConfig": "tsconfig.spec.json", 87 | "inlineStyleLanguage": "scss", 88 | "assets": ["src/favicon.ico", "src/assets"], 89 | "styles": ["src/styles.scss"], 90 | "scripts": [] 91 | } 92 | } 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 | 39 | 40 | 41 |
42 | Create a form here and save the form 46 |
47 | 51 | 52 |
53 | The output of the above Form Builder on save is a JSON Schema that 56 | represents the dynamic form. 58 |
59 | 60 |
61 |

As JSON Schema

62 |
63 |

 67 |         {{form | json }}
 68 |       
69 |
70 |
71 |
72 | 73 | 74 |
75 | You can fill up the form created in the Form Builder. Make sure to add a 78 | Submit Button in the form to submit the result. 80 |
81 | 82 | 83 |
84 | The output of the above Form on submit is a JSON Schema that represents 87 | the answers given. 89 |
90 | 91 |
92 |

Submission JSON

93 |
94 |

 98 |         {{currentSubmission | json }}
 99 |       
100 |
101 |
102 |
103 | 104 | 105 |
106 | You can read previously submitted form 110 |
111 | 117 |
118 | -------------------------------------------------------------------------------- /src/app/form-builder/options.ts: -------------------------------------------------------------------------------- 1 | const commonEditFormOptions = [ 2 | { 3 | key: 'layout', 4 | components: [ 5 | { 6 | key: 'overlay', 7 | ignore: true, 8 | }, 9 | ], 10 | }, 11 | { 12 | key: 'api', 13 | label: 'Submission', 14 | components: [ 15 | { 16 | key: 'tags', 17 | ignore: true, 18 | }, 19 | { 20 | key: 'properties', 21 | ignore: true, 22 | }, 23 | ], 24 | }, 25 | ]; 26 | 27 | const commonDisplayEditFormOptions = [ 28 | { 29 | key: 'tableView', 30 | ignore: true, 31 | }, 32 | ]; 33 | 34 | const commonDataEditFormOptions = [ 35 | { 36 | key: 'persistent', 37 | ignore: true, 38 | }, 39 | { 40 | key: 'protected', 41 | ignore: true, 42 | }, 43 | { 44 | key: 'dbIndex', 45 | ignore: true, 46 | }, 47 | { 48 | key: 'encrypted', 49 | ignore: true, 50 | }, 51 | { 52 | key: 'calculateServer', 53 | ignore: true, 54 | }, 55 | ]; 56 | 57 | const commonValidationEditFormOptions = [ 58 | { 59 | key: 'unique', 60 | ignore: true, 61 | }, 62 | ]; 63 | 64 | const defaultEditFormOptions = [ 65 | ...commonEditFormOptions, 66 | { 67 | key: 'display', 68 | components: commonDisplayEditFormOptions, 69 | }, 70 | { 71 | key: 'data', 72 | components: commonDataEditFormOptions, 73 | }, 74 | { 75 | key: 'validation', 76 | components: commonValidationEditFormOptions, 77 | }, 78 | ]; 79 | 80 | export const options = { 81 | noDefaultSubmitButton: true, 82 | builder: { 83 | basic: { 84 | components: { 85 | password: false, 86 | submitButton: { 87 | title: 'Submit Button', 88 | key: 'submitButton', 89 | icon: 'floppy-o', 90 | weight: 100, 91 | schema: { 92 | type: 'button', 93 | key: 'submit', 94 | leftIcon: 'fa fa-floppy-o', 95 | disableOnInvalid: true, 96 | }, 97 | }, 98 | resetButton: { 99 | title: 'Reset Button', 100 | key: 'resetButton', 101 | icon: 'eraser', 102 | weight: 101, 103 | schema: { 104 | label: 'Reset', 105 | type: 'button', 106 | key: 'reset', 107 | leftIcon: 'fa fa-eraser', 108 | action: 'reset', 109 | theme: 'danger', 110 | }, 111 | }, 112 | }, 113 | }, 114 | advanced: { 115 | components: { 116 | address: false, 117 | }, 118 | }, 119 | premium: false, 120 | resource: false, 121 | }, 122 | editForm: { 123 | textfield: defaultEditFormOptions, 124 | textarea: [ 125 | ...commonEditFormOptions, 126 | { 127 | key: 'display', 128 | components: [ 129 | ...commonDisplayEditFormOptions, 130 | { 131 | key: 'isUploadEnabled', 132 | ignore: true, 133 | }, 134 | ], 135 | }, 136 | { 137 | key: 'data', 138 | components: commonDataEditFormOptions, 139 | }, 140 | { 141 | key: 'validation', 142 | components: commonValidationEditFormOptions, 143 | }, 144 | ], 145 | number: defaultEditFormOptions, 146 | checkbox: [ 147 | ...commonEditFormOptions, 148 | { 149 | key: 'display', 150 | components: [ 151 | ...commonDisplayEditFormOptions, 152 | { 153 | key: 'inputType', 154 | ignore: true, 155 | }, 156 | ], 157 | }, 158 | { 159 | key: 'data', 160 | components: commonDataEditFormOptions, 161 | }, 162 | { 163 | key: 'validation', 164 | components: commonValidationEditFormOptions, 165 | }, 166 | ], 167 | selectboxes: defaultEditFormOptions, 168 | select: defaultEditFormOptions, 169 | radio: defaultEditFormOptions, 170 | button: defaultEditFormOptions, 171 | email: [ 172 | ...commonEditFormOptions, 173 | { 174 | key: 'display', 175 | components: commonDisplayEditFormOptions, 176 | }, 177 | { 178 | key: 'data', 179 | components: commonDataEditFormOptions, 180 | }, 181 | { 182 | key: 'validation', 183 | components: [ 184 | ...commonValidationEditFormOptions, 185 | { 186 | key: 'kickbox', 187 | ignore: true, 188 | }, 189 | ], 190 | }, 191 | ], 192 | url: defaultEditFormOptions, 193 | phoneNumber: defaultEditFormOptions, 194 | tags: defaultEditFormOptions, 195 | datetime: defaultEditFormOptions, 196 | day: defaultEditFormOptions, 197 | time: defaultEditFormOptions, 198 | currency: defaultEditFormOptions, 199 | signature: defaultEditFormOptions, 200 | htmlelement: defaultEditFormOptions, 201 | content: defaultEditFormOptions, 202 | columns: defaultEditFormOptions, 203 | fieldset: defaultEditFormOptions, 204 | panel: defaultEditFormOptions, 205 | well: defaultEditFormOptions, 206 | hidden: defaultEditFormOptions, 207 | container: defaultEditFormOptions, 208 | datamap: defaultEditFormOptions, 209 | datagrid: defaultEditFormOptions, 210 | editgrid: defaultEditFormOptions, 211 | }, 212 | }; 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dynamic Forms 2 | 3 | This is an Angular project consisting of two primary components: Form Builder and Form Renderer. These components enable users to create dynamic forms through a drag-and-drop interface, render the forms, submit them, and view the submissions. And all the forms and submission are stored in a JSON format. This project is built using the FORMIO library, focusing on the client-side aspects, and excludes server-side functionality. 4 | 5 | ### Demo : https://ani-dynamic-forms-demo.surge.sh/ 6 | 7 | ## Form Builder 8 | 9 | The Angular Dynamic Form Builder is a powerful tool that allows users to create forms in the UI using a drag-and-drop interface. This project provides a user-friendly way to design forms with various components and layout options and then export the form's configuration or structure in JSON format. 10 | 11 | #### Inputs & Outputs 12 | 13 | | Input/Output | Name | Description | 14 | | ------------ | ------ | --------------------------------------------------------------------------------------------------------------------- | 15 | | initialForm | Input | This is an optional input. If a JSON of a form is provided, that form will be loaded and then the user can edit that. | 16 | | save | Output | On clicking the Save Form button, this will emit the JSON of the form structure. | 17 | 18 | #### Screenshot 19 | 20 | ![](./screenshots/formBuilderBlank.png) 21 | 22 | ## Form Renderer 23 | 24 | The Angular Dynamic Form Renderer can then render the form in json format created previously by the form 25 | builder for the user to fill up. After submitting the form, a JSON will be created with the values of the submission by the Form Renderer. Then submission JSON can also be used to prefill the Form Renderer or show the submission in read only mode in the Form Renderer. 26 | 27 | #### Inputs & Outputs 28 | 29 | | Input/Output | Name | Description | 30 | | ------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | 31 | | form | Input | This is the JSON of a form structure that will be rendered. The JSON will be created by the Form Builder previously. | 32 | | submission | Input | This is an optional input. If submission JSON is provided, that form will be prefilled by that and then the user can edit that if read-only mode is not set. | 33 | | readOnly | Input | This is an optional input. If this readOnly mode is true, that form will not allow the user to edit it. | 34 | | submit | Output | On clicking the Submit button, this will emit the JSON of the form submission. | 35 | 36 | #### Screenshot 37 | 38 | ![](./screenshots/formRendererBlank.png) 39 | 40 | ## Example 41 | 42 | 1. Creating Form using Form Builder 43 | ![](./screenshots/formBuilder.png) 44 | 2. The form will be saved in JSON format 45 | 46 | ```js 47 | { 48 | "components": [ 49 | { 50 | "label": "HTML", 51 | "tag": "h2", 52 | "attrs": [ 53 | { 54 | "attr": "", 55 | "value": "" 56 | } 57 | ], 58 | "content": "A Random Form", 59 | "refreshOnChange": false, 60 | "key": "html", 61 | "type": "htmlelement", 62 | "input": false, 63 | "tableView": false 64 | }, 65 | { 66 | "label": "Name", 67 | "columns": [ 68 | { 69 | "components": [ 70 | { 71 | "label": "First Name", 72 | "applyMaskOn": "change", 73 | "validate": { 74 | "required": true 75 | }, 76 | "key": "firstName", 77 | "type": "textfield", 78 | "input": true, 79 | "tableView": true 80 | } 81 | ], 82 | "width": 4, 83 | "offset": 0, 84 | "push": 0, 85 | "pull": 0, 86 | "size": "md", 87 | "currentWidth": 4 88 | }, 89 | { 90 | "components": [ 91 | { 92 | "label": "Middle Name", 93 | "applyMaskOn": "change", 94 | "key": "middleName", 95 | "type": "textfield", 96 | "input": true, 97 | "tableView": true 98 | } 99 | ], 100 | "width": 4, 101 | "offset": 0, 102 | "push": 0, 103 | "pull": 0, 104 | "size": "md", 105 | "currentWidth": 4 106 | }, 107 | { 108 | "components": [ 109 | { 110 | "label": "Last Name", 111 | "applyMaskOn": "change", 112 | "validate": { 113 | "required": true 114 | }, 115 | "key": "firstName1", 116 | "type": "textfield", 117 | "input": true, 118 | "tableView": true 119 | } 120 | ], 121 | "size": "md", 122 | "width": 4, 123 | "offset": 0, 124 | "push": 0, 125 | "pull": 0, 126 | "currentWidth": 4 127 | } 128 | ], 129 | "key": "name", 130 | "type": "columns", 131 | "input": false, 132 | "tableView": false 133 | }, 134 | { 135 | "label": "Tabs", 136 | "components": [ 137 | { 138 | "label": "Tab 1", 139 | "key": "tab1", 140 | "components": [ 141 | { 142 | "label": "Columns", 143 | "columns": [ 144 | { 145 | "components": [ 146 | { 147 | "label": "Date of Birth", 148 | "datePicker": { 149 | "disableWeekends": false, 150 | "disableWeekdays": false 151 | }, 152 | "enableMinDateInput": false, 153 | "enableMaxDateInput": false, 154 | "key": "dateOfBirth", 155 | "type": "datetime", 156 | "input": true, 157 | "tableView": false, 158 | "widget": { 159 | "type": "calendar", 160 | "displayInTimezone": "viewer", 161 | "locale": "en", 162 | "useLocaleSettings": false, 163 | "allowInput": true, 164 | "mode": "single", 165 | "enableTime": true, 166 | "noCalendar": false, 167 | "format": "yyyy-MM-dd hh:mm a", 168 | "hourIncrement": 1, 169 | "minuteIncrement": 1, 170 | "time_24hr": false, 171 | "minDate": null, 172 | "disableWeekends": false, 173 | "disableWeekdays": false, 174 | "maxDate": null 175 | } 176 | } 177 | ], 178 | "width": 6, 179 | "offset": 0, 180 | "push": 0, 181 | "pull": 0, 182 | "size": "md", 183 | "currentWidth": 6 184 | }, 185 | { 186 | "components": [ 187 | { 188 | "label": "Phone Number", 189 | "applyMaskOn": "change", 190 | "key": "phoneNumber", 191 | "type": "phoneNumber", 192 | "input": true, 193 | "tableView": true 194 | } 195 | ], 196 | "width": 6, 197 | "offset": 0, 198 | "push": 0, 199 | "pull": 0, 200 | "size": "md", 201 | "currentWidth": 6 202 | } 203 | ], 204 | "key": "columns", 205 | "type": "columns", 206 | "input": false, 207 | "tableView": false 208 | } 209 | ] 210 | }, 211 | { 212 | "label": "Tab 2", 213 | "key": "tab2", 214 | "components": [] 215 | } 216 | ], 217 | "key": "tabs", 218 | "type": "tabs", 219 | "input": false, 220 | "tableView": false 221 | }, 222 | { 223 | "label": "Survey", 224 | "tableView": false, 225 | "questions": [ 226 | { 227 | "label": "How was the product?", 228 | "value": "howWasTheProduct", 229 | "tooltip": "" 230 | }, 231 | { 232 | "label": "How was the delivery?", 233 | "value": "howWasTheDelivery", 234 | "tooltip": "" 235 | } 236 | ], 237 | "values": [ 238 | { 239 | "label": "Very Good", 240 | "value": "veryGood", 241 | "tooltip": "" 242 | }, 243 | { 244 | "label": "Good", 245 | "value": "good", 246 | "tooltip": "" 247 | }, 248 | { 249 | "label": "Medium", 250 | "value": "medium", 251 | "tooltip": "" 252 | }, 253 | { 254 | "label": "Bad", 255 | "value": "bad", 256 | "tooltip": "" 257 | }, 258 | { 259 | "label": "Very Bad", 260 | "value": "veryBad", 261 | "tooltip": "" 262 | } 263 | ], 264 | "key": "survey", 265 | "type": "survey", 266 | "input": true 267 | }, 268 | { 269 | "label": "Signature", 270 | "key": "signature", 271 | "type": "signature", 272 | "input": true, 273 | "tableView": false 274 | }, 275 | { 276 | "label": "Columns", 277 | "columns": [ 278 | { 279 | "components": [ 280 | { 281 | "label": "Reset", 282 | "action": "reset", 283 | "showValidations": false, 284 | "theme": "danger", 285 | "block": true, 286 | "leftIcon": "fa fa-eraser", 287 | "key": "reset", 288 | "type": "button", 289 | "input": true, 290 | "tableView": false 291 | } 292 | ], 293 | "width": 6, 294 | "offset": 0, 295 | "push": 0, 296 | "pull": 0, 297 | "size": "md", 298 | "currentWidth": 6 299 | }, 300 | { 301 | "components": [ 302 | { 303 | "label": "Submit", 304 | "showValidations": false, 305 | "block": true, 306 | "leftIcon": "fa fa-floppy-o", 307 | "disableOnInvalid": true, 308 | "key": "submit", 309 | "type": "button", 310 | "input": true, 311 | "tableView": false, 312 | "saveOnEnter": false 313 | } 314 | ], 315 | "width": 6, 316 | "offset": 0, 317 | "push": 0, 318 | "pull": 0, 319 | "size": "md", 320 | "currentWidth": 6 321 | } 322 | ], 323 | "autoAdjust": true, 324 | "key": "columns1", 325 | "type": "columns", 326 | "input": false, 327 | "tableView": false 328 | } 329 | ] 330 | } 331 | ``` 332 | 333 | 3. Viewing the form and filling it up 334 | ![](./screenshots/formRendererFilled.png) 335 | 4. After submit, it will give a submission JSON 336 | 337 | ```js 338 | { 339 | "data": { 340 | "firstName": "Abc", 341 | "middleName": "", 342 | "firstName1": "Xyz", 343 | "dateOfBirth": "2023-09-05T12:00:00+05:30", 344 | "phoneNumber": "", 345 | "survey": { 346 | "howWasTheProduct": "good", 347 | "howWasTheDelivery": "medium" 348 | }, 349 | "signature": "data:image/png;base64,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", 350 | "reset": false, 351 | "submit": true 352 | }, 353 | "metadata": { 354 | "timezone": "Asia/Kolkata", 355 | "offset": 330, 356 | "origin": "https://ani-dynamic-forms-demo.surge.sh", 357 | "referrer": "", 358 | "browserName": "Netscape", 359 | "userAgent": "Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/116.0", 360 | "pathName": "/", 361 | "onLine": true 362 | }, 363 | "state": "submitted", 364 | "_vnote": "" 365 | } 366 | ``` 367 | 368 | 5. You can use the submission JSON to pre-fill the form or see it in read only mode. 369 | ![](./screenshots/formRendererReadOnly.png) 370 | 371 | ## Development server 372 | 373 | Run `npm run serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. 374 | 375 | ## Build 376 | 377 | Run `npm run build` to build the project. The build artifacts will be stored in the `dist/` directory. 378 | --------------------------------------------------------------------------------