├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── dist ├── demo.html ├── v-form-builder.common.js ├── v-form-builder.css ├── v-form-builder.umd.js └── v-form-builder.umd.min.js ├── package-lock.json ├── package.json ├── public ├── css │ ├── bootstrap-4.3.1.min.css │ ├── v-form-builder.css │ └── v-form-builder.min.css ├── favicon.ico └── index.html ├── src ├── App.vue ├── assets │ ├── .gitkeep │ └── v-form-builder.css ├── components │ ├── FormBuilder.vue │ └── FormRenderer.vue ├── configs │ ├── control-config-enum.js │ ├── controls.js │ ├── events.js │ ├── form.js │ ├── global.js │ ├── index.js │ ├── roles.js │ ├── row.js │ ├── section.js │ ├── styles.js │ └── validation.js ├── demo-form-data.js ├── extendables │ └── index.js ├── index.js ├── installer.js ├── interfaces │ ├── register-properties.interface.ts │ └── tab-row.interface.ts ├── libraries │ ├── alert-dialog.js │ ├── applier.js │ ├── classes │ │ ├── key-value-item.class.js │ │ └── validation-result.class.js │ ├── helper.js │ ├── icon-facade.js │ ├── icons │ │ ├── add-outline.icon.js │ │ ├── arrow-down.icon.js │ │ ├── arrow-up.icon.js │ │ ├── chevron-down.icon.js │ │ ├── chevron-up.icon.js │ │ ├── close.icon.js │ │ ├── cog.icon.js │ │ ├── edit-pencil.icon.js │ │ ├── information-outline.icon.js │ │ ├── navigation-more.icon.js │ │ └── trash.icon.js │ ├── list-item.class.js │ ├── modal-renderer.class.ts │ ├── sidebar-renderer.class.js │ ├── validation.js │ └── validations │ │ ├── custom-closure.js │ │ ├── is-email.js │ │ ├── max.js │ │ ├── min.js │ │ ├── regex.js │ │ ├── required.js │ │ └── same-as.js ├── main.ts ├── mixins │ ├── control-field-extend-mixin.js │ ├── control-special-config-mixin.js │ ├── form-builder-mixins.js │ ├── form-builder │ │ ├── form-builder-event-handler.js │ │ ├── form-builder-methods.js │ │ └── form-builder-model.js │ ├── form-renderer-mixins.js │ ├── form-renderer │ │ ├── configuration.js │ │ ├── model.js │ │ └── validation.js │ ├── render-row-view-mixin.js │ ├── renderer-section-view-mixin.js │ ├── row-view-mixin.js │ ├── section-sort-mixins.js │ ├── section-view-mixins.js │ ├── sidebar-body-mixin.js │ ├── style-injection-mixin.js │ └── toggleable-mixin.js ├── shims-tsx.d.ts ├── shims-vue.d.ts ├── skeletons │ ├── README.md │ └── controls │ │ ├── BaseControlConfigSkeleton.js │ │ └── BaseControlSkeleton.js └── views │ ├── README.md │ ├── builder │ ├── ControlView.vue │ ├── FormConfiguration.vue │ ├── GlobalKeyValueItemConfiguration.vue │ ├── GlobalModal.vue │ ├── GlobalSidebar.vue │ ├── SectionContainer.vue │ ├── SectionNavigationBar.vue │ ├── add-controls │ │ ├── AddControlControl.vue │ │ ├── AddControlToRowControl.vue │ │ ├── AddRowControl.vue │ │ └── AddSectionControl.vue │ ├── control-views │ │ ├── ControlLabel.vue │ │ └── ControlOption.vue │ ├── misc │ │ └── IconTooltip.vue │ ├── modal-config-views │ │ └── TabRowConfigurationView.vue │ ├── row-views │ │ ├── TabContentRowView.vue │ │ └── TableRowView.vue │ ├── section-navigation-buttons │ │ └── TabSectionPreButtons.vue │ ├── section-views │ │ ├── NormalSectionView.vue │ │ ├── TabSectionView.vue │ │ ├── TableSectionView.vue │ │ └── ToggleableSectionView.vue │ └── sidebar-config-views │ │ ├── SidebarControlConfiguration.vue │ │ ├── SidebarControlSelectList.vue │ │ ├── SidebarFormConfiguration.vue │ │ ├── SidebarSectionConfiguration.vue │ │ └── control-configuration-views │ │ ├── ControlBasicInformation.vue │ │ ├── ControlStylingInformation.vue │ │ └── ControlValidationInformation.vue │ ├── container-views │ └── SidebarToggleableContainer.vue │ ├── control-configs │ ├── ButtonConfigView.vue │ ├── DatePickerConfigView.vue │ ├── DropdownConfigView.vue │ ├── FileUploaderConfigView.vue │ ├── InputConfigView.vue │ ├── LabelConfigView.vue │ ├── NumberConfigView.vue │ ├── README.md │ ├── RadioCheckboxConfigView.vue │ ├── TextBlockConfigView.vue │ └── TextConfigView.vue │ ├── controls │ ├── ButtonControl.vue │ ├── DatePickerControl.vue │ ├── DropdownControl.vue │ ├── EmptyBlockControl.vue │ ├── FileUploaderControl.vue │ ├── InputControl.vue │ ├── LabelControl.vue │ ├── NumberControl.vue │ ├── README.md │ ├── RadioCheckboxControl.vue │ ├── TextBlockControl.vue │ └── TextControl.vue │ └── renderer │ ├── ControlView.vue │ ├── SectionContainer.vue │ ├── row-views │ └── TabContentRowView.vue │ └── section-views │ ├── NormalSectionView.vue │ ├── TabSectionView.vue │ ├── TableSectionView.vue │ └── ToggleableSectionView.vue ├── static.json ├── tsconfig.json └── vue.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 4 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | insert_final_newline = false 15 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | #github: sethsandaru 4 | ko_fi: sethphat 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | # Log files 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw? 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Phat Tran 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /dist/demo.html: -------------------------------------------------------------------------------- 1 | 2 | v-form-builder demo 3 | 4 | 5 | 6 | 7 | 8 | 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v-form-builder", 3 | "version": "2.1.1", 4 | "private": false, 5 | "description": "Vue Form Builder PRO MAX built from top of Vue. Super dynamic and flexible including Drag and Drop feature.", 6 | "author": { 7 | "email": "me@sethphat.com", 8 | "name": "Seth Chen", 9 | "url": "https://sethphat.dev" 10 | }, 11 | "scripts": { 12 | "serve": "cross-env NODE_ENV=development vue-cli-service serve --port 9999", 13 | "build": "vue-cli-service build --skip-plugins @vue/cli-plugin-eslint", 14 | "lint": "vue-cli-service lint", 15 | "build-lib": "cross-env NODE_ENV=production vue-cli-service build --target lib --name v-form-builder ./src/index.js --skip-plugins @vue/cli-plugin-eslint" 16 | }, 17 | "main": "./dist/v-form-builder.umd.min.js", 18 | "dependencies": { 19 | "core-js": "^2.6.5", 20 | "dayjs": "^1.8.28", 21 | "deep-equal": "^2.0.3", 22 | "litepicker": "^1.4.6", 23 | "vue": "^2.6.10", 24 | "vue-class-component": "^7.2.3", 25 | "vue-property-decorator": "^8.4.2", 26 | "vue-upload-component": "^2.8.22", 27 | "vuedraggable": "^2.23.2" 28 | }, 29 | "devDependencies": { 30 | "@vue/cli-plugin-babel": "^3.0.3", 31 | "@vue/cli-plugin-typescript": "^4.5.9", 32 | "@vue/cli-service": "^4.4.4", 33 | "babel-eslint": "^10.0.1", 34 | "cross-env": "^7.0.2", 35 | "eslint": "^5.16.0", 36 | "eslint-plugin-vue": "^5.0.0", 37 | "faker": "^4.1.0", 38 | "node-forge": ">=0.10.0", 39 | "typescript": "~3.9.3", 40 | "vue-template-compiler": "^2.6.10", 41 | "vue-typed-mixins": "^0.2.0" 42 | }, 43 | "postcss": { 44 | "plugins": { 45 | "autoprefixer": {} 46 | } 47 | }, 48 | "browserslist": [ 49 | "> 1%", 50 | "last 2 versions" 51 | ], 52 | "keywords": [ 53 | "vue-form-builder", 54 | "v-form-builder", 55 | "vue form builder with drag drop", 56 | "vue form builder pro max", 57 | "vue", 58 | "vuejs", 59 | "form builder", 60 | "drag & drop", 61 | "seth phat" 62 | ], 63 | "license": "MIT", 64 | "repository": { 65 | "url": "https://github.com/sethsandaru/vue-form-builder.git" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sethsandaru/vue-form-builder/537ec262b6376b41f305ff4a53699c0fbbed65db/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vue-Form-Builder v2.1.0 - github.com/sethsandaru 9 | 10 | 11 | 12 | 13 | 14 | 17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sethsandaru/vue-form-builder/537ec262b6376b41f305ff4a53699c0fbbed65db/src/assets/.gitkeep -------------------------------------------------------------------------------- /src/components/FormBuilder.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 105 | -------------------------------------------------------------------------------- /src/components/FormRenderer.vue: -------------------------------------------------------------------------------- 1 | 53 | 54 | -------------------------------------------------------------------------------- /src/configs/control-config-enum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Constant for Radio/Checkbox Styling 3 | * @type {{next: string, bothSide: string, line: string}} 4 | */ 5 | const RADIO_CHECKBOX_STYLE = { 6 | line: { 7 | val: 'line', 8 | description: "Line by Line" 9 | }, 10 | next: { 11 | val: 'next', 12 | description: "Next to each others" 13 | }, 14 | bothSide: { 15 | val: "bothSide", 16 | description: "Stay on each sides in a row (Left - Right)", 17 | } 18 | }; 19 | 20 | /** 21 | * Constant for Radio/Checkbox position 22 | * @type {{left: string, center: string, right: string}} 23 | */ 24 | const RADIO_CHECKBOX_POSITION = { 25 | left: { 26 | val: 'left', 27 | description: "Left" 28 | }, 29 | center: { 30 | val: 'center', 31 | description: 'Center', 32 | }, 33 | right: { 34 | val: 'right', 35 | description: 'Right' 36 | } 37 | }; 38 | 39 | /** 40 | * Return Type for Date-Picker 41 | * @type {{format: {val: string, description: string}, object: {val: string, description: string}}} 42 | */ 43 | const DATE_PICKER_RETURN_TYPES = { 44 | format: { 45 | val: "format", 46 | description: "Date String from Date Format" 47 | }, 48 | object: { 49 | val: "object", 50 | description: "JS-Date Object" 51 | } 52 | } 53 | 54 | /** 55 | * Date Picker Start Date (Sunday, Monday,...) of the Week 56 | */ 57 | const DATE_PICKER_START_DATES = { 58 | monday: { 59 | val: 1, 60 | description: "Monday" 61 | }, 62 | tuesday: { 63 | val: 2, 64 | description: "Tuesday" 65 | }, 66 | wednesday: { 67 | val: 3, 68 | description: "Wednesday" 69 | }, 70 | thursday: { 71 | val: 4, 72 | description: "Thursday" 73 | }, 74 | friday: { 75 | val: 5, 76 | description: "Friday" 77 | }, 78 | saturday: { 79 | val: 6, 80 | description: "Saturday" 81 | }, 82 | sunday: { 83 | val: 0, 84 | description: "Sunday" 85 | }, 86 | } 87 | 88 | /** 89 | * DROPDOWN DATA MODE 90 | * - Normal 91 | * - API 92 | */ 93 | const DROPDOWN_DATA_MODES = { 94 | list: { 95 | val: 'list', 96 | description: "Normal - Pre-Config List Items" 97 | }, 98 | api: { 99 | val: 'api', 100 | description: "API - List Items from your own API", 101 | }, 102 | } 103 | 104 | /** 105 | * File Upload Modes 106 | */ 107 | const FILE_UPLOAD_MODES = { 108 | normal: { 109 | val: 'normal', 110 | description: "Keep the file there for HTTP Form Request" 111 | }, 112 | 113 | preUpload: { 114 | val: 'preUpload', 115 | description: "Pre-Upload to your own API" 116 | }, 117 | } 118 | 119 | export { 120 | RADIO_CHECKBOX_POSITION, 121 | RADIO_CHECKBOX_STYLE, 122 | 123 | DATE_PICKER_RETURN_TYPES, 124 | DATE_PICKER_START_DATES, 125 | 126 | DROPDOWN_DATA_MODES, 127 | 128 | FILE_UPLOAD_MODES 129 | } -------------------------------------------------------------------------------- /src/configs/events.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Event-Communication Constants in Vue-Form-Builder 3 | * @author Phat Tran 4 | */ 5 | 6 | const EVENT_CONSTANTS = { 7 | /** 8 | * Constants for Form-Builder 9 | */ 10 | BUILDER: { 11 | SECTION: { 12 | ADDED_ROW: "builder.section.added_row", 13 | DELETE: "builder.section.delete", 14 | UPDATE: "builder.section.update", 15 | 16 | // section sort 17 | PUSH: "builder.section.push" 18 | }, 19 | 20 | 21 | ROW: { 22 | CREATE: "builder.row.create", 23 | UPDATE: "builder.row.update", 24 | DELETE: "builder.row.delete", 25 | DELETED: "builder.row.deleted", 26 | 27 | // For Tab View 28 | ADD_TAB: "builder.row.add_tab", 29 | }, 30 | 31 | CONTROL: { 32 | CREATE: "builder.control.create", 33 | 34 | UPDATE: "builder.control.update", 35 | 36 | DELETE: "builder.control.delete", 37 | DELETED: "builder.control.deleted", 38 | 39 | SORT: "builder.control.delete", 40 | }, 41 | 42 | /** 43 | * GLOBAL SIDEBAR EVENTS 44 | */ 45 | SIDEBAR: { 46 | INJECT: "builder.sidebar.inject", 47 | OPEN: "builder.sidebar.open", 48 | OPENED: "builder.sidebar.opened", 49 | 50 | SAVE: "builder.sidebar.save", 51 | SAVE_AND_CLOSE: "builder.sidebar.save_and_close", 52 | 53 | AFTER_CLOSED: "builder.sidebar.after_closed", 54 | }, 55 | 56 | /** 57 | * GLOBAL MODAL EVENTS 58 | */ 59 | MODAL: { 60 | INJECT: "builder.modal.inject", 61 | OPEN: "builder.modal.open", 62 | OPENED: "builder.modal.opened", 63 | 64 | SAVE: "builder.modal.save", 65 | SAVE_AND_CLOSE: "builder.modal.save_and_close", 66 | 67 | AFTER_CLOSED: "builder.modal.after_closed", 68 | }, 69 | }, 70 | 71 | /** 72 | * Constants for Form-Renderer 73 | */ 74 | RENDERER: { 75 | RUN_VALIDATION: "renderer.run_validation", 76 | VALIDATION_OK: "renderer.validation_ok", 77 | VALIDATION_FAILED: "renderer.validation_failed", 78 | } 79 | }; 80 | 81 | export { 82 | EVENT_CONSTANTS 83 | } -------------------------------------------------------------------------------- /src/configs/form.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Form-Configuration for Vue-Form-Builder 3 | * @author Phat Tran 4 | */ 5 | 6 | const FORM_DEFAULT_DATA = { 7 | headline: "", 8 | subHeadline: "", 9 | isShowHeadline: false, 10 | 11 | //
tag ?? 12 | renderFormTag: false, 13 | formActionURL: "", 14 | formMethod: "POST", 15 | 16 | // Server-Side validation 17 | enableServerSideValidation: false, 18 | serverSideValidationEndpoint: "" 19 | }; 20 | 21 | export { 22 | FORM_DEFAULT_DATA 23 | } 24 | -------------------------------------------------------------------------------- /src/configs/global.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const GLOBAL_CONFIG = { 4 | rendererFormId: "vue-form-render-pro-max", // ID of
for Renderer 5 | }; 6 | 7 | export { 8 | GLOBAL_CONFIG 9 | } -------------------------------------------------------------------------------- /src/configs/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Main Configuration of the Vue-Form-Builder 3 | * @author Phat Tran 4 | */ 5 | 6 | const MAIN_CONSTANTS = { 7 | COPYRIGHT: "Vue-Form-Builder v2.1.1 - github.com/sethsandaru with Love <3", 8 | } 9 | 10 | export { 11 | MAIN_CONSTANTS 12 | } -------------------------------------------------------------------------------- /src/configs/roles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Roles of Form Builder 3 | * @author Phat Tran 4 | */ 5 | 6 | export default { 7 | // global 8 | canEditFormConfigurations: true, 9 | 10 | // sections 11 | canAddSection: true, 12 | canEditSection: true, 13 | canDeleteSection: true, 14 | canReOrderingSection: true, 15 | 16 | // rows/tab 17 | canAddRow: true, 18 | canEditRow: true, 19 | canDeleteRow: true, 20 | canReOrderingRow: true, 21 | 22 | // controls 23 | canAddControl: true, 24 | canEditControl: true, 25 | canDeleteControl: true, 26 | canReOrderingControl: true, 27 | 28 | // control-inner 29 | canUpdateControlBasicDetail: true, 30 | canUpdateControlStyling: true, 31 | canUpdateControlValidation: true, 32 | canUpdateControlSpecialConfiguration: true, 33 | 34 | } -------------------------------------------------------------------------------- /src/configs/row.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Row Constants in Vue-Form-Builder 3 | * @author Phat Tran 4 | */ 5 | 6 | import {HELPER} from "@/libraries/helper"; 7 | 8 | 9 | const ROW_TYPES = { 10 | normal: 'normal', // Normal Row in Div (div.row) 11 | tabRow: 'tabRow', // Tab Row 12 | tableRow: 'tableRow', // Table row (tr) 13 | }; 14 | 15 | const ROW_DEFAULT_DATA = { 16 | uniqueId: '', 17 | additionalClass: '', 18 | type: ROW_TYPES.normal, 19 | sortOrder: 0, 20 | controls: [], // ids of control 21 | extendData: null, // depends on the row, we would have specific configuration 22 | }; 23 | 24 | /** 25 | * Create new Row Object 26 | * @param {string} type 27 | * @param {any} extendData 28 | */ 29 | function createNewRow(type, extendData = null) { 30 | if (!ROW_TYPES[type]) { 31 | throw new TypeError(`Row Type: ${type} doesn't exists in Vue-Form-Builder`); 32 | } 33 | 34 | // create new section data base on the default data 35 | let newRowObject = HELPER.cloneDeep(ROW_DEFAULT_DATA) 36 | newRowObject.type = type 37 | newRowObject.uniqueId = "row-" + HELPER.getUUIDv4() 38 | newRowObject.extendData = extendData 39 | 40 | return newRowObject 41 | } 42 | 43 | 44 | export { 45 | ROW_TYPES, 46 | ROW_DEFAULT_DATA, 47 | createNewRow 48 | } 49 | -------------------------------------------------------------------------------- /src/configs/section.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Supported Section & Section Constants in Vue-Form-Builder 3 | * @author Phat Tran 4 | */ 5 | import {HELPER} from "@/libraries/helper"; 6 | import {ROW_TYPES} from "@/configs/row"; 7 | 8 | // Builder Views 9 | import NormalSectionView from "@/views/builder/section-views/NormalSectionView"; 10 | // import TableSectionView from "@/views/builder/section-views/TableSectionView"; 11 | import ToggleableSectionView from "@/views/builder/section-views/ToggleableSectionView"; 12 | import TabSectionView from "@/views/builder/section-views/TabSectionView"; 13 | 14 | // Builder Buttons [PRE] 15 | import TabSectionPreButtons from "@/views/builder/section-navigation-buttons/TabSectionPreButtons"; 16 | 17 | // Builder Buttons [POST] 18 | 19 | // Renderer Views 20 | import RendererNormalSectionView from "@/views/renderer/section-views/NormalSectionView"; 21 | import RendererToggleableSectionView from "@/views/renderer/section-views/ToggleableSectionView"; 22 | import RendererTabSectionView from "@/views/renderer/section-views/TabSectionView"; 23 | 24 | 25 | 26 | 27 | const SECTION_TYPES = { 28 | normal: { 29 | name: "Normal Block", 30 | description: "Normal block with a headline", 31 | value: 'normal', 32 | 33 | rowType: ROW_TYPES.normal, 34 | builderView: NormalSectionView, 35 | rendererView: RendererNormalSectionView, 36 | }, 37 | 38 | toggleable: { 39 | name: "Toggleable Block", 40 | description: "Section block with toggle (display/hide) feature", 41 | value: 'toggleable', 42 | 43 | rowType: ROW_TYPES.normal, 44 | builderView: ToggleableSectionView, 45 | rendererView: RendererToggleableSectionView 46 | }, 47 | 48 | tab: { 49 | name: "Tab Block", 50 | description: "A block with multiple tabs feature", 51 | value: "tab", 52 | 53 | rowType: ROW_TYPES.tabRow, 54 | builderView: TabSectionView, 55 | rendererView: RendererTabSectionView, 56 | preCustomButtonView: TabSectionPreButtons, 57 | } 58 | 59 | // table: { 60 | // name: "Table Block", 61 | // description: "Section block built from a table with 2 column", 62 | // value: 'table', 63 | // 64 | // rowType: ROW_TYPES.tableRow, 65 | // builderView: TableSectionView 66 | // }, 67 | 68 | 69 | }; 70 | 71 | /** 72 | * DEFAULT DATA in order to create/reread from the configuration 73 | */ 74 | const SECTION_DEFAULT_DATA = { 75 | uniqueId: '', 76 | 77 | headline: '', 78 | headlineAdditionalClass: '', 79 | 80 | subHeadline: '', 81 | subHeadlineAdditionalClass: '', 82 | 83 | isShowHeadline: true, 84 | 85 | sortOrder: 0, 86 | type: '', 87 | rows: [], // array of rowId 88 | controls: [], // array of controlIds 89 | }; 90 | 91 | /** 92 | * Create new Section 93 | * @param type 94 | * @param sortOrder 95 | */ 96 | function createNewSection(type, sortOrder = 0) { 97 | if (!SECTION_TYPES[type]) { 98 | throw new TypeError(`Section Type: ${type} doesn't exists in Vue-Form-Builder`); 99 | } 100 | 101 | // create new section data base on the default data 102 | let newSectionData = HELPER.cloneDeep(SECTION_DEFAULT_DATA) 103 | newSectionData.type = type 104 | newSectionData.uniqueId = "section-" + HELPER.getUUIDv4() 105 | newSectionData.headline = "New Section" 106 | newSectionData.subHeadline = "This is the sub-headline of the new section" 107 | newSectionData.sortOrder = sortOrder 108 | 109 | return newSectionData; 110 | } 111 | 112 | export { 113 | SECTION_TYPES, 114 | SECTION_DEFAULT_DATA, 115 | createNewSection 116 | } -------------------------------------------------------------------------------- /src/configs/styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Styling for Vue-Form-Builder 3 | * Mostly it about the Container class. 4 | * 5 | * The classes can be re-placed in run-time (For other CSS framework if you want) 6 | * But the main ideas is following Bootstrap, hopefully yours framework is have a same structural. 7 | * 8 | * @author Phat Tran 9 | */ 10 | 11 | const STYLES = { 12 | CONTAINER: { 13 | FLUID: "container-fluid md-layout", 14 | NORMAL: "container" 15 | }, 16 | 17 | ROW: "row md-layout", 18 | 19 | COLUMNS: { 20 | COL1: "col-md-1 md-layout-item md-size-5", 21 | COL2: "col-md-2 md-layout-item md-size-10", 22 | COL3: "col-md-3 md-layout-item md-size-25", 23 | COL4: "col-md-4 md-layout-item md-size-33", 24 | COL5: "col-md-5 md-layout-item md-size-40", 25 | COL6: "col-md-6 md-layout-item md-size-50", 26 | COL7: "col-md-7 md-layout-item md-size-60", 27 | COL8: "col-md-8 md-layout-item md-size-66", 28 | COL9: "col-md-9 md-layout-item md-size-75", 29 | COL10: "col-md-10 md-layout-item md-size-90", 30 | COL11: "col-md-11 md-layout-item md-size-95", 31 | COL12: "col-md-12 md-layout-item md-size-100", 32 | }, 33 | 34 | /** 35 | * List Group Classes - Used in AddSectionVueControl 36 | */ 37 | LIST_GROUP: { 38 | CONTAINER: "list-group md-list", // div.list-group 39 | SINGLE_ITEM: "list-group-item list-group-item-action md-list-item md-list-item-action", //a[href=...].list-group-item.list-group-item-action 40 | }, 41 | 42 | /** 43 | * Button Classes 44 | */ 45 | BUTTON: { 46 | PRIMARY: "btn btn-primary md-button md-raised md-primary md-theme-default", 47 | SECONDARY: "btn btn-secondary md-button md-raised md-secondary md-theme-default", 48 | DEFAULT: "btn btn-default md-button md-raised md-default md-theme-default", 49 | SUCCESS: "btn btn-success md-button md-raised md-success md-theme-default", 50 | DANGER: "btn btn-danger md-button md-raised md-accent md-theme-default", 51 | WARNING: "btn btn-warning md-button md-raised md-warning md-theme-default", 52 | INFO: "btn btn-info md-button md-raised md-info md-theme-default", 53 | }, 54 | 55 | /** 56 | * Form Classes 57 | */ 58 | FORM: { 59 | FORM_GROUP: "form-group", 60 | INPUT_GROUP: "input-group", 61 | FORM_CONTROL: "form-control md-field", 62 | ERROR_OUTLINE: "is-invalid md-error", 63 | ERROR_MESSAGE: "invalid-feedback" 64 | }, 65 | 66 | /** 67 | * Table Classes 68 | */ 69 | TABLE: { 70 | TABLE_CLASS: "table md-table", 71 | TR_CLASS: "md-table-row", 72 | TD_CLASS: "md-table-cell" 73 | }, 74 | 75 | }; 76 | 77 | export { 78 | STYLES 79 | } -------------------------------------------------------------------------------- /src/configs/validation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Validation Rules of Vue-Form-Builder 3 | * - desc: Validation Rule Info 4 | * - needValue - boolean: Need additional value from user or not? True is yes 5 | * - errorMessage: default error message, can be edit by the users 6 | * - 7 | */ 8 | 9 | const VALIDATION_RULES = { 10 | required: { 11 | desc: "Field must have value (at least length must be 1)", 12 | needValue: false, 13 | errorMessage: "This field is required", 14 | }, 15 | 16 | min: { 17 | desc: "Minimum length of the value. For number field, it will be the value (not length)", 18 | 19 | needValue: true, 20 | valueInfo: "Number", 21 | 22 | errorMessage: "Minimum value for this field is :min", 23 | }, 24 | 25 | max: { 26 | desc: "Maximum length of the value. For number field, it will be the value (not length)", 27 | 28 | needValue: true, 29 | valueInfo: "Number", 30 | 31 | errorMessage: "Maximum value for this field is :max", 32 | }, 33 | 34 | isEmail: { 35 | desc: "Validate email address", 36 | needValue: false, 37 | errorMessage: "Wrong email address format", 38 | }, 39 | 40 | regex: { 41 | desc: "Validation the field by using your own Regex Rule", 42 | 43 | needValue: true, 44 | valueInfo: "Regex String|Flag - Eg: [0-9]+|g", 45 | 46 | errorMessage: "This field value doesn't match with the rule", 47 | }, 48 | 49 | sameAs: { 50 | desc: "Check if the field has same value with another field", 51 | 52 | needValue: true, 53 | valueInfo: "The field name you want to check with", 54 | 55 | errorMessage: "This field value doesn't as same as :sameAs", 56 | }, 57 | 58 | customClosure: { 59 | desc: "Invoke your own method to check your field", 60 | 61 | needValue: true, 62 | valueInfo: "Your method name", 63 | 64 | errorMessage: "Custom validation failed.", 65 | } 66 | } 67 | 68 | /** 69 | * Add Validation Rule for the Control Validation 70 | * @param ruleType 71 | * @returns {{errorMessage: (string), type: (string)}} 72 | */ 73 | class ValidationRule { 74 | /** 75 | * Needed properties 76 | */ 77 | ruleType = "" 78 | errorMessage = "" 79 | additionalValue = "" 80 | 81 | constructor(ruleType, customErrorMessage) { 82 | this.ruleType = ruleType 83 | 84 | if (ruleType) { 85 | this.errorMessage = customErrorMessage || VALIDATION_RULES[ruleType].errorMessage || "" 86 | } 87 | } 88 | } 89 | 90 | export 91 | { 92 | VALIDATION_RULES, 93 | ValidationRule 94 | } -------------------------------------------------------------------------------- /src/extendables/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Extendable Main File 3 | * @desc This is where I'm going to register all the Reusable/Extendable/Vue-Plugin for the library 4 | * @author Phat Tran 5 | */ 6 | 7 | import Vue from 'vue' 8 | 9 | /** 10 | * Import libraries/plugins goes here 11 | * import x from 'y'; 12 | */ 13 | 14 | /** 15 | * Vue Register Goes Here 16 | * Vue.plugin(x) 17 | */ 18 | Vue.property.$event = new Vue(); 19 | 20 | /** 21 | * Please remember to always keep a newline after registered 22 | */ -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import {VueFormBuilderInstaller} from "@/installer"; 2 | import FormBuilder from "@/components/FormBuilder"; 3 | import FormRenderer from "@/components/FormRenderer"; 4 | import BaseControlSkeleton from "@/skeletons/controls/BaseControlSkeleton"; 5 | import BaseControlConfigSkeleton from "@/skeletons/controls/BaseControlConfigSkeleton"; 6 | 7 | import '@/assets/v-form-builder.css' 8 | 9 | // Create module definition for Vue.use() 10 | const VueFormBuilderPlugin = { 11 | install: VueFormBuilderInstaller 12 | }; 13 | 14 | // For Browser-Vue's purpose 15 | // Export the VueFormBuilderPlugin to let developers register it later. 16 | // I won't automatically register it, therefore you would have a big chance to 17 | // - Configure the Internal Configuration of the Form 18 | // - Extendable (Registering more controls, Styling Classes,...) 19 | // - ... 20 | if (typeof window !== 'undefined') { 21 | window.VueFormBuilderPlugin = VueFormBuilderPlugin 22 | } else if (typeof global !== 'undefined') { 23 | global.VueFormBuilderPlugin = VueFormBuilderPlugin 24 | } 25 | 26 | export { 27 | VueFormBuilderPlugin, 28 | 29 | // Main Component for Node 30 | FormBuilder, 31 | FormRenderer, 32 | 33 | // For Vue.extend 34 | BaseControlSkeleton, 35 | BaseControlConfigSkeleton 36 | } -------------------------------------------------------------------------------- /src/installer.js: -------------------------------------------------------------------------------- 1 | import {FormIcon} from "@/libraries/icon-facade"; 2 | import FormBuilder from "@/components/FormBuilder"; 3 | import FormRenderer from "@/components/FormRenderer"; 4 | import {CONTROLS} from "@/configs/controls"; 5 | import {STYLES} from "@/configs/styles"; 6 | import {VALIDATION_RULES} from "@/configs/validation"; 7 | import {IRegisterProperties} from "@/interfaces/register-properties.interface.ts"; 8 | 9 | const VueFormBuilderInstaller = function( 10 | Vue, 11 | properties = {} 12 | ) { 13 | if (VueFormBuilderInstaller.installed) { 14 | return 15 | } 16 | 17 | /** 18 | * 19 | * @type {IRegisterProperties} 20 | */ 21 | const defaultProperties = { 22 | globalInjection : true, 23 | validationErrorShowAlert: true, 24 | validationErrorAlertText: "Your form got error(s), please fix it and submit again" 25 | }; 26 | 27 | Object.assign(properties, defaultProperties); 28 | 29 | // DI for Form-Builder 30 | const formDI = { 31 | getIcon: FormIcon.getSVG, // a method to get icon from IconFacade 32 | }; 33 | 34 | // control extend? 35 | if (properties.hasOwnProperty('controls')) { 36 | extendingControls(properties.controls) 37 | } 38 | 39 | // style override? 40 | if (properties.hasOwnProperty('styles')) { 41 | Object.assign(STYLES, properties.styles) 42 | } 43 | 44 | // validation extend? 45 | if (properties.hasOwnProperty('validations')) { 46 | extendingValidations(properties.validations) 47 | } 48 | 49 | // validation closures 50 | if (properties.hasOwnProperty('validationClosures')) { 51 | formDI.validationClosures = properties.validationClosures 52 | } 53 | 54 | // show alert or not? 55 | formDI.validationErrorShowAlert = properties.validationErrorShowAlert || true 56 | formDI.validationErrorAlertText = properties.validationErrorAlertText 57 | 58 | // disable control? 59 | if (properties.disableControls && properties.disableControls.length) { 60 | disableControls(properties.disableControls) 61 | } 62 | 63 | // For Event-Bus purpose 64 | Vue.prototype.$formEvent = new Vue() 65 | Vue.prototype.$form = formDI 66 | 67 | // Register Form-Components 68 | if (!properties.hasOwnProperty('globalInjection') || properties.globalInjection) { 69 | Vue.component('FormBuilder', FormBuilder); 70 | Vue.component('FormRenderer', FormRenderer); 71 | } 72 | 73 | // Mark as registered 74 | VueFormBuilderInstaller.installed = true; 75 | } 76 | 77 | /** 78 | * Extending Control from the users 79 | * @param {Object} moreControlObject 80 | */ 81 | const extendingControls = function(moreControlObject) { 82 | // validation if it does conflict or not 83 | const allKeys = Object.keys(moreControlObject) 84 | for (let iKey = 0; iKey < allKeys.length; iKey++) { 85 | let key = allKeys[iKey] 86 | 87 | // duplicated => error 88 | if (CONTROLS.hasOwnProperty(key)) { 89 | throw new TypeError(`Extend-Control-Error: Your '${key}' control is duplicated with our build-in Controls. Please change to another key name instead.`); 90 | } 91 | } 92 | 93 | // eligible to extend now 94 | Object.assign(CONTROLS, moreControlObject) 95 | } 96 | 97 | /** 98 | * Extending Validation 99 | * @param {Object} validationObj 100 | */ 101 | const extendingValidations = function (validationObj) { 102 | // validation if it does conflict or not 103 | const allKeys = Object.keys(validationObj) 104 | for (let iKey = 0; iKey < allKeys.length; iKey++) { 105 | let key = allKeys[iKey] 106 | 107 | // duplicated => error 108 | if (VALIDATION_RULES.hasOwnProperty(key)) { 109 | throw new TypeError(`Extend-Validation-Error: Your '${key}' validation is duplicated with our build-in Validation. Please change to another key name instead.`); 110 | } 111 | } 112 | 113 | // eligible to extend now 114 | Object.assign(VALIDATION_RULES, validationObj) 115 | } 116 | 117 | /** 118 | * Disable a list of controls by key 119 | * @param {String[]} controlKeys 120 | */ 121 | const disableControls = function(controlKeys) { 122 | controlKeys.forEach( 123 | controlKey => CONTROLS[controlKey].isHidden = true 124 | ); 125 | } 126 | 127 | export { 128 | VueFormBuilderInstaller 129 | } -------------------------------------------------------------------------------- /src/interfaces/register-properties.interface.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface IRegisterProperties { 3 | globalInjection: boolean 4 | validationErrorShowAlert: boolean 5 | validationErrorAlertText: string, 6 | disableControls: Array 7 | } -------------------------------------------------------------------------------- /src/interfaces/tab-row.interface.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface TabRow { 3 | tabName: string 4 | tabIcon?: string 5 | defaultChecked: boolean 6 | } -------------------------------------------------------------------------------- /src/libraries/alert-dialog.js: -------------------------------------------------------------------------------- 1 | 2 | const ALERT_DIALOG = { 3 | /** 4 | * Show Alert Dialog 5 | * @param message 6 | * @param stopTime 7 | */ 8 | show(message, stopTime = 3000) { 9 | const alert = document.createElement('div') 10 | alert.className = 'v-form-alert' 11 | alert.innerText = message 12 | 13 | document.getElementsByTagName('body')[0].append( 14 | alert 15 | ) 16 | 17 | setTimeout(function () { 18 | alert.remove() 19 | }, stopTime || 3000) 20 | } 21 | } 22 | 23 | export { 24 | ALERT_DIALOG 25 | } -------------------------------------------------------------------------------- /src/libraries/applier.js: -------------------------------------------------------------------------------- 1 | import {SECTION_DEFAULT_DATA} from "@/configs/section"; 2 | import {CONTROL_DEFAULT_DATA, CONTROLS} from "@/configs/controls"; 3 | import {FORM_DEFAULT_DATA} from "@/configs/form"; 4 | import {HELPER} from "@/libraries/helper"; 5 | import {ROW_DEFAULT_DATA} from "@/configs/row"; 6 | 7 | 8 | /** 9 | * Applier is an extending-object to make sure your old form-configuration still working well with the new version 10 | * @author Phat Tran 11 | * @param {{formConfig: Object, sections: Array, rows: Object, controls: Object}|undefined} formConfigObject 12 | * @return {Object} Final Object that can always use with the Form-Builder/Renderer 13 | */ 14 | const dataApplier = function(formConfigObject) { 15 | let appliedObject = { 16 | formConfig: {}, 17 | sections: {}, 18 | rows: {}, 19 | controls: {} 20 | }; 21 | 22 | // base-created-form 23 | if (!formConfigObject) { 24 | appliedObject.formConfig = HELPER.cloneDeep(FORM_DEFAULT_DATA) 25 | return appliedObject 26 | } 27 | 28 | // Form-Config Apply 29 | appliedObject.formConfig = Object.assign({}, FORM_DEFAULT_DATA, formConfigObject.formConfig) 30 | 31 | // Section(s) Apply 32 | for (let [sectionId, sectionObject] of Object.entries(formConfigObject.sections)) { 33 | appliedObject.sections[sectionId] = baseObjectExtend(SECTION_DEFAULT_DATA, sectionObject) 34 | } 35 | 36 | // Row(s) Apply 37 | for (let [rowId, rowObject] of Object.entries(formConfigObject.rows)) { 38 | appliedObject.rows[rowId] = baseObjectExtend(ROW_DEFAULT_DATA, rowObject) 39 | } 40 | 41 | // Control(s) Apply 42 | for (let [controlId, controlObject] of Object.entries(formConfigObject.controls)) { 43 | // get type - pick up config of type - merge it with the base 44 | let type = controlObject.type 45 | let baseConfigOfType = CONTROLS[type].configData 46 | let baseDefaultConfig = baseObjectExtend(CONTROL_DEFAULT_DATA, baseConfigOfType) 47 | 48 | // add to base 49 | appliedObject.controls[controlId] = Object.assign(baseDefaultConfig, controlObject) 50 | } 51 | 52 | return appliedObject 53 | } 54 | 55 | /** 56 | * From A Base Object - We Clone and Extending from a different Object 57 | * @param {Object} baseObject 58 | * @param {Object} fromObject 59 | * @returns {Object} 60 | */ 61 | function baseObjectExtend(baseObject, fromObject) { 62 | const clonedData = HELPER.cloneDeep(baseObject) 63 | return Object.assign(clonedData, fromObject) 64 | } 65 | 66 | export { 67 | dataApplier 68 | } -------------------------------------------------------------------------------- /src/libraries/classes/key-value-item.class.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default class KeyValueItem { 4 | constructor(key = null, value = null) { 5 | this.key = key 6 | this.value = value 7 | } 8 | } -------------------------------------------------------------------------------- /src/libraries/classes/validation-result.class.js: -------------------------------------------------------------------------------- 1 | import {VALIDATION_RULES} from "@/configs/validation"; 2 | 3 | export default class ValidationResult { 4 | hasError = false 5 | errorBuckets = {} 6 | 7 | /** 8 | * Check if the validation is successfully or not 9 | * @returns {boolean} 10 | */ 11 | errors() { 12 | return this.hasError 13 | } 14 | 15 | /** 16 | * Add error and generate error message 17 | * @param {String} fieldName 18 | * @param {ValidationRule} validationRule 19 | */ 20 | addError(fieldName, validationRule) { 21 | this.hasError = true 22 | 23 | if (!this.errorBuckets[fieldName]) { 24 | this.errorBuckets[fieldName] = []; 25 | } 26 | 27 | // generate error message and add it 28 | let errorMessage = validationRule.errorMessage || VALIDATION_RULES[validationRule.ruleType].errorMessage 29 | 30 | // if it has replaceable variable => replace it (:mix, :max, :lol) 31 | const replaceableVar = `:${validationRule.ruleType}` 32 | if (errorMessage.indexOf(replaceableVar) >= 0) { 33 | errorMessage = errorMessage.replace(replaceableVar, validationRule.additionalValue) 34 | } 35 | 36 | // add the error message 37 | this.errorBuckets[fieldName].push(errorMessage) 38 | } 39 | } -------------------------------------------------------------------------------- /src/libraries/helper.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const HELPER = {}; 4 | 5 | /** 6 | * Deep-Clone an Object 7 | * @param obj 8 | * @returns {any} 9 | */ 10 | HELPER.cloneDeep = function(obj) { 11 | return JSON.parse(JSON.stringify(obj)); 12 | } 13 | 14 | /** 15 | * Get UUIDv4 16 | * @returns {String} 17 | */ 18 | HELPER.getUUIDv4 = function() { 19 | return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c => 20 | (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) 21 | ); 22 | } 23 | 24 | /** 25 | * Find inside object/array by a specific rule 26 | * @param {Array|Object} collection 27 | * @param {string} ruleKey 28 | * @param {string} value 29 | * @returns {any|undefined} first item that matched the rule or undefined 30 | * @complexity O(n) for normal cases, best case will be O(1) or O(logn) 31 | */ 32 | HELPER.find = function(collection, ruleKey, value) { 33 | 34 | // Only array has `length` property 35 | if (collection.length) { 36 | // array 37 | return collection.find(item => item[ruleKey] == value) 38 | } 39 | 40 | // object traversal 41 | let keys = Object.keys(collection) 42 | for (const objKey of keys) { 43 | let data = collection[objKey] 44 | if (data && data[ruleKey] == value) { 45 | return data; 46 | } 47 | } 48 | 49 | return undefined 50 | } 51 | 52 | /** 53 | * Find array by a specific rule and return the index 54 | * @param {Array} array 55 | * @param {string} ruleKey - If it's undefined, we check the item only 56 | * @param {string} value 57 | * @returns {number} first item that matched the rule or -1 58 | * @complexity O(n) for normal cases, best case will be O(1) or O(logn) 59 | */ 60 | HELPER.findIndex = function (array, ruleKey, value) { 61 | return array.findIndex(item => { 62 | if (!ruleKey) 63 | return item == value 64 | 65 | return item[ruleKey] == value 66 | }) 67 | } 68 | 69 | export { 70 | HELPER 71 | } -------------------------------------------------------------------------------- /src/libraries/icon-facade.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Form-Icon-Facade is a lightweight library to help you retain ICON inside Form-Builder v2 3 | * @author Phat Tran 4 | * @license From Zondicons of Steve Schoger. Thanks very much for the beautiful/lightweight icons 5 | * @iconHomePage https://www.zondicons.com/ 6 | */ 7 | import {ARROW_UP_ICON} from "@/libraries/icons/arrow-up.icon"; 8 | import {ARROW_DOWN_ICON} from "@/libraries/icons/arrow-down.icon"; 9 | import {EDIT_PENCIL_ICON} from "@/libraries/icons/edit-pencil.icon"; 10 | import {COG_ICON} from "@/libraries/icons/cog.icon"; 11 | import {ADD_OUTLINE_ICON} from "@/libraries/icons/add-outline.icon"; 12 | import {CLOSE_ICON} from "@/libraries/icons/close.icon"; 13 | import {TRASH_ICON} from "@/libraries/icons/trash.icon"; 14 | import {CHEVRON_UP_ICON} from "@/libraries/icons/chevron-up.icon"; 15 | import {CHEVRON_DOWN_ICON} from "@/libraries/icons/chevron-down.icon"; 16 | import {NAVIGATION_MORE_ICON} from "@/libraries/icons/navigation-more.icon"; 17 | import {INFORMATION_OUTLINE_ICON} from "@/libraries/icons/information-outline.icon"; 18 | 19 | 20 | const ICONS = { 21 | addOutline: ADD_OUTLINE_ICON, 22 | arrowUp: ARROW_UP_ICON, 23 | arrowDown: ARROW_DOWN_ICON, 24 | editPencil: EDIT_PENCIL_ICON, 25 | cog: COG_ICON, 26 | close: CLOSE_ICON, 27 | trash: TRASH_ICON, 28 | chevronUp: CHEVRON_UP_ICON, 29 | chevronDown: CHEVRON_DOWN_ICON, 30 | navigationMore: NAVIGATION_MORE_ICON, 31 | informationOutline: INFORMATION_OUTLINE_ICON 32 | } 33 | 34 | const FormIcon = { 35 | /** 36 | * Get SVG Icon for Form-Builder 37 | * @param {String} iconName 38 | * @param {String} width - Width with px (Eg: 16px) 39 | * @param {String} height - Height with px (Eg: 16px) 40 | * @param {String} fillColor - Hex Color String (Eg: #ffffff) 41 | * @returns {string} of SVG HTML TAG 42 | */ 43 | getSVG( 44 | iconName, 45 | width = '16px', 46 | height = '16px', 47 | fillColor = '#ffffff' 48 | ) { 49 | if (!ICONS[iconName]) { 50 | throw new TypeError(`Icon Name '${iconName}' doesn't exists in Vue-Form-Builder.`); 51 | } 52 | 53 | let replacedIconWithData = ICONS[iconName] 54 | .replace("{0}", width) 55 | .replace("{1}", height) 56 | .replace("{2}", fillColor) 57 | 58 | return replacedIconWithData 59 | } 60 | }; 61 | 62 | export { 63 | FormIcon 64 | } -------------------------------------------------------------------------------- /src/libraries/icons/add-outline.icon.js: -------------------------------------------------------------------------------- 1 | 2 | const ADD_OUTLINE_ICON = ` 3 | 4 | 5 | 6 | ` 7 | 8 | export { 9 | ADD_OUTLINE_ICON 10 | } -------------------------------------------------------------------------------- /src/libraries/icons/arrow-down.icon.js: -------------------------------------------------------------------------------- 1 | 2 | const ARROW_DOWN_ICON = ` 3 | 4 | 5 | 6 | ` 7 | 8 | export { 9 | ARROW_DOWN_ICON 10 | } 11 | -------------------------------------------------------------------------------- /src/libraries/icons/arrow-up.icon.js: -------------------------------------------------------------------------------- 1 | 2 | const ARROW_UP_ICON = ` 3 | 4 | 5 | 6 | ` 7 | 8 | export { 9 | ARROW_UP_ICON 10 | } -------------------------------------------------------------------------------- /src/libraries/icons/chevron-down.icon.js: -------------------------------------------------------------------------------- 1 | 2 | const CHEVRON_DOWN_ICON = ` 3 | 4 | 5 | 6 | ` 7 | 8 | export { 9 | CHEVRON_DOWN_ICON 10 | } -------------------------------------------------------------------------------- /src/libraries/icons/chevron-up.icon.js: -------------------------------------------------------------------------------- 1 | 2 | const CHEVRON_UP_ICON = ` 3 | 4 | 5 | 6 | ` 7 | 8 | export { 9 | CHEVRON_UP_ICON 10 | } -------------------------------------------------------------------------------- /src/libraries/icons/close.icon.js: -------------------------------------------------------------------------------- 1 | 2 | const CLOSE_ICON = ` 3 | 4 | 5 | 6 | ` 7 | 8 | export { 9 | CLOSE_ICON 10 | } -------------------------------------------------------------------------------- /src/libraries/icons/cog.icon.js: -------------------------------------------------------------------------------- 1 | 2 | const COG_ICON = ` 3 | 4 | 5 | 6 | ` 7 | 8 | export { 9 | COG_ICON 10 | } -------------------------------------------------------------------------------- /src/libraries/icons/edit-pencil.icon.js: -------------------------------------------------------------------------------- 1 | 2 | const EDIT_PENCIL_ICON = ` 3 | 4 | 5 | 6 | ` 7 | 8 | export { 9 | EDIT_PENCIL_ICON 10 | } -------------------------------------------------------------------------------- /src/libraries/icons/information-outline.icon.js: -------------------------------------------------------------------------------- 1 | 2 | const INFORMATION_OUTLINE_ICON = ` 3 | 4 | 5 | 6 | ` 7 | 8 | export { 9 | INFORMATION_OUTLINE_ICON 10 | } 11 | -------------------------------------------------------------------------------- /src/libraries/icons/navigation-more.icon.js: -------------------------------------------------------------------------------- 1 | 2 | const NAVIGATION_MORE_ICON = ` 3 | 4 | 5 | 6 | ` 7 | 8 | export { 9 | NAVIGATION_MORE_ICON 10 | } 11 | -------------------------------------------------------------------------------- /src/libraries/icons/trash.icon.js: -------------------------------------------------------------------------------- 1 | 2 | const TRASH_ICON = ` 3 | 4 | 5 | 6 | ` 7 | 8 | export { 9 | TRASH_ICON 10 | } -------------------------------------------------------------------------------- /src/libraries/list-item.class.js: -------------------------------------------------------------------------------- 1 | /** 2 | * List-Item for Checkbox/Radio/Select 3 | */ 4 | export default class ListItem { 5 | value = "" 6 | text = "" 7 | 8 | constructor(value, text) { 9 | this.value = value 10 | this.text = text 11 | } 12 | } -------------------------------------------------------------------------------- /src/libraries/modal-renderer.class.ts: -------------------------------------------------------------------------------- 1 | export default class ModalRenderer { 2 | constructor( 3 | public runnerId : string, 4 | public component : any = {}, 5 | public data : any = {} 6 | ) {} 7 | } -------------------------------------------------------------------------------- /src/libraries/sidebar-renderer.class.js: -------------------------------------------------------------------------------- 1 | 2 | export default class SidebarRenderer { 3 | runnerId = '' // to recognize which components are triggering... 4 | component = null 5 | data = {} 6 | 7 | constructor( 8 | runnerId, 9 | component, 10 | data = {} 11 | ) { 12 | this.runnerId = runnerId 13 | this.component = component 14 | this.data = data 15 | } 16 | } -------------------------------------------------------------------------------- /src/libraries/validation.js: -------------------------------------------------------------------------------- 1 | import ValidationResult from "@/libraries/classes/validation-result.class"; 2 | import requiredRule from "@/libraries/validations/required"; 3 | import minRule from "@/libraries/validations/min"; 4 | import maxRule from "@/libraries/validations/max"; 5 | import isEmailRule from "@/libraries/validations/is-email"; 6 | import sameAsRule from "@/libraries/validations/same-as"; 7 | import customClosureRule from "@/libraries/validations/custom-closure"; 8 | import isRegexPassed from "@/libraries/validations/regex"; 9 | 10 | export default class Validation { 11 | rules = null 12 | valueContainer = null 13 | customClosures = {} 14 | 15 | /** 16 | * Validation Result. Always create a new instance every time the validation is run 17 | * @type {ValidationResult} 18 | */ 19 | validationResult = null 20 | 21 | /** 22 | * Create a new Validation handler 23 | * @param {Object} valueContainer 24 | * @param {Object} controls 25 | * @param {Object} definedClosures 26 | */ 27 | constructor(valueContainer, controls, definedClosures = {}) { 28 | this.valueContainer = valueContainer 29 | this.validationClosures = definedClosures 30 | this.setRules(controls) 31 | } 32 | 33 | /** 34 | * Set validation rules from the controls 35 | * @param {{validations: ValidationRule[]}} controls 36 | */ 37 | setRules(controls) { 38 | const rules = {} 39 | 40 | // traversal all control and pick the validations info 41 | Object.entries(controls).forEach(controlInfo => { 42 | let [controlId, controlItem] = controlInfo 43 | let controlName = controlItem.name || controlId 44 | 45 | // no name => this field didn't have value 46 | if (!this.valueContainer.hasOwnProperty(controlName)) { 47 | return 48 | } 49 | 50 | rules[controlName] = controlItem.validations 51 | }) 52 | 53 | this.rules = rules 54 | } 55 | 56 | /** 57 | * Start a validation check 58 | * @return {ValidationResult} 59 | */ 60 | run() { 61 | this.validationResult = new ValidationResult() 62 | const controlKeys = Object.keys(this.rules) 63 | 64 | for (const key of controlKeys) { 65 | // pickup basic data 66 | const controlValue = this.valueContainer[key] 67 | const controlRules = this.rules[key] || [] 68 | 69 | // no rule no run 70 | if (!controlRules.length) { 71 | continue; 72 | } 73 | 74 | /** 75 | * start the validation process by each rules added for the control 76 | */ 77 | for (const validationRule of controlRules) { 78 | const status = this._singleRuleRun(validationRule, controlValue) 79 | if (!status) { 80 | this.validationResult.addError(key, validationRule) 81 | } 82 | } 83 | } 84 | 85 | return this.validationResult 86 | } 87 | 88 | /** 89 | * Run single rule to check 90 | * @param {ValidationRule} validationRule 91 | * @param {any} fieldValue 92 | * @private 93 | */ 94 | _singleRuleRun(validationRule, fieldValue) { 95 | switch (validationRule.ruleType) { 96 | 97 | case "required": 98 | return requiredRule(fieldValue) 99 | 100 | case "min": 101 | return minRule(fieldValue, validationRule.additionalValue) 102 | 103 | case "max": 104 | return maxRule(fieldValue, validationRule.additionalValue) 105 | 106 | case "isEmail": 107 | return isEmailRule(fieldValue) 108 | 109 | case "sameAs": 110 | return sameAsRule(fieldValue, validationRule.additionalValue, this.valueContainer) 111 | 112 | case "customClosure": 113 | return customClosureRule(fieldValue, validationRule.additionalValue, this.valueContainer, this.customClosures) 114 | 115 | case "regex": 116 | return isRegexPassed(fieldValue, validationRule.additionalValue) 117 | 118 | default: 119 | throw new TypeError(`This validation type ${validationRule.ruleType} is not supported.`); 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /src/libraries/validations/custom-closure.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Run custom closure of to check 3 | * @param {any} fieldValue 4 | * @param {any} customClosureName 5 | * @param {Object} valuesContainer 6 | * @param {Object} customClosures 7 | * @return {boolean} 8 | */ 9 | export default function customClosureRule( 10 | fieldValue, 11 | customClosureName, 12 | valuesContainer, 13 | customClosures 14 | ) { 15 | // since there no closure registered, no need to run and it always true 16 | if (typeof customClosures[customClosureName] !== 'function') { 17 | console.error(`Custom Validation Closure ${customClosureName} does not exists. Bypassed`); 18 | return true 19 | } 20 | 21 | const closure = customClosures[customClosureName]; 22 | 23 | /** 24 | * Custom closure can access field value & valuesContainer as well 25 | */ 26 | const result = closure(fieldValue, valuesContainer); 27 | 28 | // because it might be null/undefined => falsy will be false 29 | if (!result) { 30 | return false 31 | } 32 | 33 | return true 34 | } -------------------------------------------------------------------------------- /src/libraries/validations/is-email.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if the field is truly email or not 3 | * @param {any} fieldValue 4 | * @return {boolean} 5 | */ 6 | export default function isEmailRule(fieldValue) { 7 | const REGEX_RULE = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/; 8 | return REGEX_RULE.test(fieldValue) 9 | } -------------------------------------------------------------------------------- /src/libraries/validations/max.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check max value for a field 3 | * - Text => max length 4 | * - Number => max value 5 | * - Checkboxes => max selected items 6 | * @param {any} fieldValue 7 | * @param {number} constraintValue 8 | * @return {boolean} 9 | */ 10 | export default function maxRule(fieldValue, constraintValue) { 11 | const minVal = parseInt(constraintValue) 12 | 13 | // let's check - first is array 14 | if (Array.isArray(fieldValue)) { 15 | return fieldValue.length <= minVal 16 | } 17 | 18 | // second, number 19 | if (typeof fieldValue === 'number') { 20 | return fieldValue <= minVal 21 | } 22 | 23 | // lastly, string 24 | return fieldValue.length <= minVal 25 | } -------------------------------------------------------------------------------- /src/libraries/validations/min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check min value for a field 3 | * - Text => min length 4 | * - Number => min value 5 | * - Checkboxes => min selected items 6 | * @param {any} fieldValue 7 | * @param {number} constraintValue 8 | * @return {boolean} 9 | */ 10 | export default function minRule(fieldValue, constraintValue) { 11 | const minVal = parseInt(constraintValue) 12 | 13 | // let's check - first is array 14 | if (Array.isArray(fieldValue)) { 15 | return fieldValue.length >= minVal 16 | } 17 | 18 | // second, number 19 | // second, number 20 | if (typeof fieldValue === 'number') { 21 | return fieldValue >= minVal 22 | } 23 | 24 | // lastly, string 25 | return fieldValue.length >= minVal 26 | } -------------------------------------------------------------------------------- /src/libraries/validations/regex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check if the field is truly email or not 3 | * @param {any} fieldValue 4 | * @param {string} regexRule - RegexRule|Flag. Note: RegexRule without / at the beginning and the end 5 | * @return {boolean} 6 | */ 7 | export default function isRegexPassed(fieldValue, regexRule) { 8 | const splitRules = regexRule.split("|"); 9 | 10 | const strRegexRule = splitRules[0] 11 | const strRegexFlag = splitRules[1] || "g"; 12 | 13 | const regExp = new RegExp(strRegexRule, strRegexFlag); 14 | return regExp.test(fieldValue) 15 | } -------------------------------------------------------------------------------- /src/libraries/validations/required.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Require check for validation 3 | * @param {any} fieldValue 4 | * @return {boolean} 5 | */ 6 | export default function requiredRule(fieldValue) { 7 | // for checkboxes / multiple dropdown 8 | if (Array.isArray(fieldValue)) { 9 | return fieldValue.length > 0 10 | } 11 | 12 | // for text/number/any... 13 | if (fieldValue === "") { 14 | return false 15 | } 16 | 17 | return true 18 | } -------------------------------------------------------------------------------- /src/libraries/validations/same-as.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Same-As check (field must be same as another field in the form) 3 | * @param {any} fieldValue 4 | * @param {any} fieldToCheck 5 | * @param {Object} valuesContainer 6 | * @return {boolean} 7 | */ 8 | export default function sameAsRule(fieldValue, fieldToCheck, valuesContainer) { 9 | if (!valuesContainer[fieldToCheck]) { 10 | return false 11 | } 12 | 13 | return fieldValue === valuesContainer[fieldToCheck] 14 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * NOTE - This file only exists for development purpose. 3 | * It will help me to run and test the form 4 | */ 5 | 6 | 7 | import Vue from 'vue' 8 | import App from './App.vue' 9 | // @ts-ignore 10 | import {VueFormBuilderPlugin} from "@/index"; 11 | import {IRegisterProperties} from "@/interfaces/register-properties.interface"; 12 | 13 | Vue.config.productionTip = false 14 | 15 | const configOptions : IRegisterProperties = { 16 | disableControls: [], // ['input', 'number'] 17 | globalInjection: true, 18 | validationErrorShowAlert: true, 19 | validationErrorAlertText: "Please check the error messages and solve it." 20 | }; 21 | 22 | Vue.use(VueFormBuilderPlugin, configOptions) 23 | 24 | new Vue({ 25 | render: h => h(App) 26 | }).$mount('#app') 27 | -------------------------------------------------------------------------------- /src/mixins/control-field-extend-mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base Setup for any `controls` of Control in Vue-Form-Builder 3 | * @example InputControl - use the mixin. I'll keep our code extendable as possible 4 | */ 5 | import {STYLE_INJECTION_MIXIN} from "@/mixins/style-injection-mixin"; 6 | 7 | const EMIT_EVENT = "change"; 8 | 9 | const CONTROL_FIELD_EXTEND_MIXIN = { 10 | mixins: [STYLE_INJECTION_MIXIN], 11 | 12 | props: { 13 | // control configuration 14 | control: { 15 | type: Object, 16 | required: true 17 | }, 18 | 19 | // v-model value 20 | value: null, // any types 21 | }, 22 | 23 | // global data-field - available to override 24 | data: () =>({ 25 | stopDefaultValueAssign: false 26 | }), 27 | 28 | /** 29 | * For V-Model Purpose 30 | * Basically, we will emit the 'change' to the parent to keep the update... 31 | */ 32 | model: { 33 | event: EMIT_EVENT, 34 | props: "value" 35 | }, 36 | 37 | watch: { 38 | /** 39 | * Watch if there is new data being assigned 40 | * @param val 41 | */ 42 | value(val) { 43 | this.setValue(val) 44 | } 45 | }, 46 | 47 | methods: { 48 | /** 49 | * Run this to emit the value to the parent 50 | * @param val 51 | */ 52 | updateValue(val) { 53 | this.$emit(EMIT_EVENT, val) 54 | }, 55 | 56 | /** 57 | * Need-To-Override Method - Set Value. 58 | * Set value from parent to the current field/control 59 | */ 60 | setValue(val) {return val} // NEED TO OVERRIDE 61 | }, 62 | 63 | computed: { 64 | /** 65 | * Class for Field (input) 66 | * @returns {(string)[]} 67 | */ 68 | controlFieldClass() { 69 | return [ 70 | this.styles.FORM.FORM_CONTROL, 71 | this.control.additionalFieldClass 72 | ] 73 | }, 74 | 75 | /** 76 | * Control Name 77 | * @returns {*|string|string} 78 | */ 79 | controlName() { 80 | return this.control.name || this.control.uniqueId 81 | } 82 | }, 83 | 84 | /** 85 | * Global post-mounted processing 86 | */ 87 | mounted() { 88 | // default set value 89 | if ( 90 | this.stopDefaultValueAssign === false && 91 | !this.value && 92 | this.control.defaultValue 93 | ) { 94 | this.updateValue(this.control.defaultValue) 95 | } 96 | }, 97 | } 98 | 99 | export { 100 | CONTROL_FIELD_EXTEND_MIXIN 101 | } -------------------------------------------------------------------------------- /src/mixins/control-special-config-mixin.js: -------------------------------------------------------------------------------- 1 | import {STYLE_INJECTION_MIXIN} from "@/mixins/style-injection-mixin"; 2 | import IconTooltip from "@/views/builder/misc/IconTooltip"; 3 | 4 | 5 | const CONTROL_SPECIAL_CONFIG_MIXIN = { 6 | components: {IconTooltip}, 7 | mixins: [STYLE_INJECTION_MIXIN], 8 | 9 | props: { 10 | /** 11 | * Control Object Configuration 12 | */ 13 | control: { 14 | type: Object, 15 | required: true 16 | }, 17 | 18 | /** 19 | * Form-Data from the ROOT 20 | */ 21 | formData: { 22 | type: Object, 23 | required: true 24 | } 25 | }, 26 | 27 | } 28 | 29 | export{ 30 | CONTROL_SPECIAL_CONFIG_MIXIN 31 | } -------------------------------------------------------------------------------- /src/mixins/form-builder-mixins.js: -------------------------------------------------------------------------------- 1 | import {FORM_BUILDER_EVENT_HANDLER} from "@/mixins/form-builder/form-builder-event-handler"; 2 | import {FORM_BUILDER_METHODS} from "@/mixins/form-builder/form-builder-methods"; 3 | import {FORM_BUILDER_MODEL} from "@/mixins/form-builder/form-builder-model"; 4 | import {STYLE_INJECTION_MIXIN} from "@/mixins/style-injection-mixin"; 5 | 6 | 7 | export default [ 8 | FORM_BUILDER_EVENT_HANDLER, 9 | FORM_BUILDER_METHODS, 10 | FORM_BUILDER_MODEL, 11 | STYLE_INJECTION_MIXIN 12 | ] -------------------------------------------------------------------------------- /src/mixins/form-builder/form-builder-methods.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [Note] Do not use this mixin for other purpose. This is where I move all the code of FormBuilder to keep easy to: 3 | * - Structuring 4 | * - Refactoring 5 | * - ... 6 | * This file will keep 90% methods of Form-Builder 7 | * @author Phat Tran 8 | */ 9 | 10 | import {createNewSection, SECTION_DEFAULT_DATA} from "@/configs/section"; 11 | import {dataApplier} from "@/libraries/applier"; 12 | 13 | const FORM_BUILDER_METHODS = { 14 | data:() => ({ 15 | sortedSections: [], 16 | }), 17 | 18 | methods: { 19 | /** 20 | * Do Mapping Before Rendering/Showing Up 21 | */ 22 | mapping(value) { 23 | this.formData = Object.assign({}, this.formData, dataApplier(value)) 24 | this.doSortSection() 25 | }, 26 | 27 | /** 28 | * Create Default Form-Config-Data on a new section... 29 | */ 30 | createDefaultData() { 31 | this.formData = Object.assign({}, dataApplier(this.formData)); 32 | }, 33 | 34 | /** 35 | * Create a new Blank Section into the Big `sections` 36 | */ 37 | addSection(sectionType) { 38 | let newSortOrder = Object.keys(this.formData.sections).length + 1; 39 | let sectionObject = createNewSection(sectionType, newSortOrder) 40 | 41 | // we have to push it from $set otherwise it will not reactive (LOL) 42 | this.$set(this.formData.sections, sectionObject.uniqueId, sectionObject) 43 | this.doSortSection() 44 | }, 45 | 46 | /** 47 | * Sort Section and Cache it... 48 | */ 49 | doSortSection() { 50 | this.sortedSections = []; 51 | 52 | for (let [sectionId, sectionObject] of Object.entries(this.formData.sections)) { 53 | this.sortedSections.push(sectionObject) 54 | } 55 | 56 | this.sortedSections.sort((a, b) => { 57 | return a.sortOrder - b.sortOrder; 58 | }) 59 | } 60 | }, 61 | } 62 | 63 | export { 64 | FORM_BUILDER_METHODS 65 | } -------------------------------------------------------------------------------- /src/mixins/form-builder/form-builder-model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * [Note] Do not use this mixin for other purpose. This is where I move all the code of FormBuilder to keep easy to: 3 | * - Structuring 4 | * - Refactoring 5 | * - ... 6 | * This file will be handle the v-model of the FormBuilder. 7 | * @author Phat Tran 8 | */ 9 | const EMIT_EVENT = "change"; 10 | const deepEqual = require('deep-equal') // TO CHECK THE DEEPEST VALUES OF THE FORM... 11 | 12 | const FORM_BUILDER_MODEL = { 13 | props: { 14 | value: Object, 15 | }, 16 | model: { 17 | event: EMIT_EVENT, 18 | props: "value" 19 | }, 20 | 21 | watch: { 22 | /** 23 | * For Update New Configuration After User Changed the Form 24 | */ 25 | formData: { 26 | deep: true, // deep watcher - because we have a long-tree object 27 | handler(newFormData) { 28 | this.$emit(EMIT_EVENT, newFormData) 29 | } 30 | }, 31 | 32 | /** 33 | * For Update the New Configuration After User Applied new DATA into v-model 34 | */ 35 | value: { 36 | deep:true, 37 | handler(newFormData, oldFormData) { 38 | // because this is in the initialize => no data at first 39 | if (typeof oldFormData === 'undefined') { 40 | return 41 | } 42 | 43 | // we have to create a new formConfig for the "unexpected value" like: {}, null, undefined 44 | // only available for null and empty object data 45 | if (!newFormData || !Object.keys(newFormData).length) { 46 | return this.mapping() 47 | } 48 | 49 | // this time object have data, we have to make sure everything 50 | if (deepEqual(newFormData, oldFormData)) { 51 | return 52 | } 53 | 54 | // okay this time object is fully new and we need to do mapping again 55 | return this.mapping(newFormData) 56 | } 57 | } 58 | }, 59 | }; 60 | 61 | export { 62 | FORM_BUILDER_MODEL 63 | } -------------------------------------------------------------------------------- /src/mixins/form-renderer-mixins.js: -------------------------------------------------------------------------------- 1 | import {MODEL} from "@/mixins/form-renderer/model"; 2 | import {CONFIGURATION} from "@/mixins/form-renderer/configuration"; 3 | import {STYLE_INJECTION_MIXIN} from "@/mixins/style-injection-mixin"; 4 | import {FORM_BUILDER_METHODS} from "@/mixins/form-builder/form-builder-methods"; 5 | import {VALIDATION_MIXIN} from "@/mixins/form-renderer/validation"; 6 | 7 | 8 | export default [ 9 | CONFIGURATION, 10 | MODEL, 11 | STYLE_INJECTION_MIXIN, 12 | FORM_BUILDER_METHODS, 13 | VALIDATION_MIXIN 14 | ] -------------------------------------------------------------------------------- /src/mixins/form-renderer/configuration.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Form-Renderer Configuration Handler 3 | */ 4 | import {GLOBAL_CONFIG} from "@/configs/global"; 5 | 6 | const deepEqual = require('deep-equal') // TO CHECK THE DEEPEST VALUES OF THE FORM... 7 | 8 | const CONFIGURATION = { 9 | props: { 10 | /** 11 | * The main form configuration that generated from FormBuilder 12 | */ 13 | formConfiguration: { 14 | type: Object, 15 | required: true, 16 | }, 17 | 18 | /** 19 | * Read-only mode 20 | * Show up text instead of input controls 21 | */ 22 | readOnly: { 23 | type: Boolean, 24 | default: () => false, 25 | }, 26 | }, 27 | 28 | watch: { 29 | /** 30 | * Watching the configuration, if user change it => need to re-updated the form itself 31 | */ 32 | formConfiguration: { 33 | deep: true, 34 | handler(val) { 35 | if (deepEqual(val, this.formData)) { 36 | return 37 | } 38 | 39 | this.mapping(val) 40 | this.createValueContainer(val) 41 | } 42 | }, 43 | }, 44 | 45 | created() { 46 | this.mapping(this.formConfiguration) 47 | }, 48 | 49 | computed: { 50 | /** 51 | * Get the id 52 | * @returns {string} 53 | */ 54 | formTagId: () => GLOBAL_CONFIG.rendererFormId 55 | } 56 | }; 57 | 58 | export { 59 | CONFIGURATION 60 | } -------------------------------------------------------------------------------- /src/mixins/form-renderer/model.js: -------------------------------------------------------------------------------- 1 | /** 2 | * V-Model of Form-Renderer 3 | */ 4 | import {CONTROLS} from "@/configs/controls"; 5 | 6 | const EMIT_EVENT = "change"; 7 | 8 | const MODEL = { 9 | props: { 10 | value: Object, 11 | }, 12 | model: { 13 | event: EMIT_EVENT, 14 | props: "value" 15 | }, 16 | data: () => ({ 17 | valueContainer: null 18 | }), 19 | watch: { 20 | value: { 21 | deep: true, 22 | handler(val) { 23 | if (typeof val !== 'object') { 24 | return 25 | } 26 | 27 | // set value for fields 28 | Object.assign(this.valueContainer, val) 29 | } 30 | }, 31 | 32 | valueContainer: { 33 | deep: true, 34 | handler(val) { 35 | if (!val) return; 36 | 37 | this.$emit(EMIT_EVENT, this.valueContainer) 38 | }, 39 | } 40 | }, 41 | methods: { 42 | /** 43 | * Create Value Container to Store the Data 44 | */ 45 | createValueContainer() { 46 | let containerObj = {} 47 | const controlIds = Object.keys(this.formData.controls) 48 | 49 | controlIds.forEach(controlId => { 50 | const controlItem = this.formData.controls[controlId] 51 | 52 | // if disableValue is provided, we don't need to solve more for the control 53 | if ( 54 | typeof CONTROLS[controlItem.type].disableValue === 'boolean' && 55 | CONTROLS[controlItem.type].disableValue 56 | ) { 57 | return 58 | } 59 | 60 | // get the key-name value 61 | let name = controlItem.name; 62 | if (!name) { 63 | // fall-back to id if no name 64 | name = controlId; 65 | } 66 | 67 | // add new empty field value 68 | containerObj[name] = "" 69 | 70 | // if the control has default factory creation closure, run it 71 | if (typeof CONTROLS[controlItem.type].rendererDefaultData === 'function') { 72 | containerObj[name] = CONTROLS[controlItem.type].rendererDefaultData(controlItem) 73 | } 74 | }); 75 | 76 | // emit to the parent for the value detail 77 | if (this.value) { 78 | this.$emit(EMIT_EVENT, containerObj) 79 | } 80 | 81 | // set to the real handler 82 | this.valueContainer = containerObj 83 | } 84 | }, 85 | 86 | created() { 87 | this.createValueContainer() 88 | }, 89 | } 90 | 91 | export { 92 | MODEL 93 | } -------------------------------------------------------------------------------- /src/mixins/form-renderer/validation.js: -------------------------------------------------------------------------------- 1 | import Validation from "@/libraries/validation"; 2 | import {EVENT_CONSTANTS} from "@/configs/events"; 3 | import {ALERT_DIALOG} from "@/libraries/alert-dialog"; 4 | 5 | const VALIDATION_MIXIN = { 6 | data: () => ({ 7 | validationErrors: {}, 8 | }), 9 | 10 | methods: { 11 | /** 12 | * 13 | * @param {Object} errorBucket {controlName: [array of message]} 14 | */ 15 | setValidationError(errorBucket) { 16 | this.$formEvent.$emit(EVENT_CONSTANTS.RENDERER.VALIDATION_FAILED, true) 17 | 18 | // use set for reactive... 19 | this.$set(this, 'validationErrors', errorBucket) 20 | 21 | if (this.$form.validationErrorShowAlert) { 22 | ALERT_DIALOG.show(this.$form.validationErrorAlertText) 23 | } 24 | }, 25 | 26 | /** 27 | * Run the validation process 28 | */ 29 | async runValidation() { 30 | // always clear validation before run... 31 | this.$set(this, 'validationErrors', {}) 32 | 33 | // run the validation 34 | const result = this.$form.Validation.run() 35 | 36 | // field-error handling 37 | if (result.errors()) { 38 | return this.setValidationError(result.errorBuckets) 39 | } 40 | 41 | // if we turned on the server-side validation 42 | if (this.isEnableServerSideValidation) { 43 | const isServersideValidationOk = await this.requestForServerSideValidation() 44 | if (!isServersideValidationOk) { 45 | return; 46 | } 47 | } 48 | 49 | // ok emit to all listener if they want to know the validation is ok or not 50 | this.$formEvent.$emit(EVENT_CONSTANTS.RENDERER.VALIDATION_OK, true) 51 | }, 52 | 53 | /** 54 | * Run the server-side validation 55 | * 56 | * @returns {Promise} 57 | */ 58 | async requestForServerSideValidation() { 59 | const validationResult = await fetch(this.serverSideValidationEndpoint, { 60 | method: "POST", 61 | headers: { 62 | 'Content-Type': 'application/json; charset=utf-8', 63 | 'Accept': 'application/json' 64 | }, 65 | body: this.valueContainer 66 | }) 67 | 68 | // oke validation success 69 | if (validationResult.ok) { 70 | return true; 71 | } 72 | 73 | // I consider 422 is failed the validation 74 | if (validationResult.status === 422) { 75 | // the body should contains error messages 76 | const errorsBag = await validationResult.json() 77 | this.setValidationError(errorsBag) 78 | 79 | return false 80 | } 81 | }, 82 | }, 83 | 84 | /** 85 | * Dependencies Injection into the Form-Renderer. 86 | */ 87 | created() { 88 | // create validation instance 89 | this.$form.Validation = new Validation( 90 | this.valueContainer, 91 | this.formData.controls, 92 | this.$form.validationClosures || {}, 93 | ) 94 | 95 | // listen to validation invoke 96 | this.$formEvent.$on(EVENT_CONSTANTS.RENDERER.RUN_VALIDATION, this.runValidation); 97 | }, 98 | 99 | computed: { 100 | /** 101 | * to check if enable server side validation 102 | * 103 | * @returns {Boolean} 104 | */ 105 | isEnableServerSideValidation() { 106 | if (!this.formConfiguration.formConfig || !this.formConfiguration.formConfig.enableServerSideValidation) { 107 | return false; 108 | } 109 | 110 | return this.formConfiguration.formConfig.enableServerSideValidation; 111 | }, 112 | 113 | /** 114 | * Quick access to the endpoint 115 | * 116 | * @returns {any} 117 | */ 118 | serverSideValidationEndpoint() { 119 | return this.formConfiguration.formConfig.serverSideValidationEndpoint 120 | } 121 | }, 122 | } 123 | 124 | export {VALIDATION_MIXIN} -------------------------------------------------------------------------------- /src/mixins/render-row-view-mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base extendation for the Row-View - Form-Builder 3 | */ 4 | import {STYLE_INJECTION_MIXIN} from "@/mixins/style-injection-mixin"; 5 | import ControlView from "@/views/renderer/ControlView"; 6 | 7 | const RENDERER_ROW_VIEW_MIXIN = { 8 | components: { 9 | ControlView 10 | }, 11 | mixins: [STYLE_INJECTION_MIXIN], 12 | 13 | props: { 14 | /** 15 | * Section that belong to the current row 16 | */ 17 | section: { 18 | type: Object, 19 | required: true 20 | }, 21 | 22 | /** 23 | * Current row data 24 | */ 25 | row: { 26 | type: Object, 27 | required: true 28 | }, 29 | 30 | /** 31 | * Controls of Form 32 | */ 33 | controls: { 34 | type: Object, 35 | required: true, 36 | }, 37 | 38 | valueContainer: Object, 39 | validationErrors: Object, 40 | readOnly: Boolean, 41 | }, 42 | 43 | computed: { 44 | 45 | /** 46 | * Check if we had controls 47 | * @returns {boolean} 48 | */ 49 | hasControls() { 50 | return this.row.controls.length > 0 51 | }, 52 | 53 | /** 54 | * Classes for draggable 55 | * @returns {(string|string)[]} 56 | */ 57 | containerClasses() { 58 | return [ 59 | this.styles.ROW, 60 | 'control-list-container', 61 | ( 62 | this.hasControls 63 | ? '' 64 | : 'empty' 65 | ) 66 | ] 67 | }, 68 | }, 69 | } 70 | export { 71 | RENDERER_ROW_VIEW_MIXIN 72 | } -------------------------------------------------------------------------------- /src/mixins/renderer-section-view-mixin.js: -------------------------------------------------------------------------------- 1 | import ControlView from "@/views/renderer/ControlView"; 2 | import {STYLE_INJECTION_MIXIN} from "@/mixins/style-injection-mixin"; 3 | 4 | const RENDERER_SECTION_VIEW_MIXIN = { 5 | components: { 6 | ControlView // Show Control 7 | }, 8 | 9 | mixins: [STYLE_INJECTION_MIXIN], 10 | 11 | props: { 12 | section: Object, 13 | rows: Object, 14 | controls: Object, 15 | valueContainer: Object, 16 | validationErrors: Object, 17 | readOnly: Boolean, 18 | }, 19 | 20 | data: () => ({ 21 | 22 | }), 23 | 24 | methods: { 25 | 26 | }, 27 | 28 | computed: { 29 | 30 | 31 | /** 32 | * Classes for draggable 33 | * @returns {(string|string)[]} 34 | */ 35 | containerClasses() { 36 | return [ 37 | this.styles.ROW, 38 | 'control-list-container' 39 | ] 40 | } 41 | }, 42 | }; 43 | 44 | export { 45 | RENDERER_SECTION_VIEW_MIXIN 46 | } -------------------------------------------------------------------------------- /src/mixins/row-view-mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base extendation for the Row-View - Form-Builder 3 | */ 4 | import {STYLE_INJECTION_MIXIN} from "@/mixins/style-injection-mixin"; 5 | import draggable from 'vuedraggable' 6 | import AddControlToRowControl from "@/views/builder/add-controls/AddControlToRowControl"; 7 | import ControlView from "@/views/builder/ControlView"; 8 | 9 | const ROW_VIEW_MIXIN = { 10 | components: { 11 | draggable, 12 | AddControlToRowControl, 13 | ControlView 14 | }, 15 | mixins: [STYLE_INJECTION_MIXIN], 16 | 17 | props: { 18 | /** 19 | * Section that belong to the current row 20 | */ 21 | section: { 22 | type: Object, 23 | required: true 24 | }, 25 | 26 | /** 27 | * Current row data 28 | */ 29 | row: { 30 | type: Object, 31 | required: true 32 | }, 33 | 34 | /** 35 | * Controls of Form 36 | */ 37 | controls: { 38 | type: Object, 39 | required: true, 40 | }, 41 | 42 | permissions: Object 43 | }, 44 | 45 | computed: { 46 | /** 47 | * Property that will be used to drag - for Control Only 48 | */ 49 | dragControlHandle() { 50 | return ".option-control.drag-item" 51 | }, 52 | 53 | /** 54 | * Base group of drag/drop 55 | * We can share this for each section/row 56 | */ 57 | dragGroup() { 58 | return "v-form-builder-control" 59 | }, 60 | 61 | /** 62 | * Classes for draggable 63 | * @returns {(string|string)[]} 64 | */ 65 | draggableClasses() { 66 | return [ 67 | this.styles.ROW, 68 | 'control-list-container', 69 | ( 70 | this.hasControls 71 | ? '' 72 | : 'empty' 73 | ) 74 | ] 75 | }, 76 | 77 | /** 78 | * Check if we had controls 79 | * @returns {boolean} 80 | */ 81 | hasControls() { 82 | return this.row.controls.length > 0 83 | } 84 | }, 85 | } 86 | export { 87 | ROW_VIEW_MIXIN 88 | } -------------------------------------------------------------------------------- /src/mixins/section-sort-mixins.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Section Sort Mixins 3 | * @desc Where I handle the sorting (push up/down) for the Section 4 | * @used SectionNavigationBar 5 | * @author Phat Tran 6 | */ 7 | import {EVENT_CONSTANTS} from "@/configs/events"; 8 | 9 | const SECTION_SORT_MIXINS = { 10 | methods: { 11 | /** 12 | * Push-up Section 13 | * @desc Send Fire Event to Parent to handle the Push-Up of Current Section 14 | * @listener FormBuilder 15 | */ 16 | pushUp() { 17 | this.$formEvent.$emit(EVENT_CONSTANTS.BUILDER.SECTION.PUSH, this.section, 0) 18 | }, 19 | 20 | /** 21 | * Push-down Section 22 | * @desc Send Fire Event to Parent to handle the Push-Down of Current Section 23 | * @listener FormBuilder 24 | */ 25 | pushDown() { 26 | this.$formEvent.$emit(EVENT_CONSTANTS.BUILDER.SECTION.PUSH, this.section, 1) 27 | } 28 | } 29 | } 30 | 31 | 32 | export { 33 | SECTION_SORT_MIXINS 34 | } -------------------------------------------------------------------------------- /src/mixins/section-view-mixins.js: -------------------------------------------------------------------------------- 1 | import AddRowControl from "@/views/builder/add-controls/AddRowControl"; 2 | import {createNewRow} from "@/configs/row"; 3 | import {SECTION_TYPES} from "@/configs/section"; 4 | import {EVENT_CONSTANTS} from "@/configs/events"; 5 | import draggable from 'vuedraggable' 6 | import AddControlControl from "@/views/builder/add-controls/AddControlControl"; 7 | import ControlView from "@/views/builder/ControlView"; 8 | import {STYLE_INJECTION_MIXIN} from "@/mixins/style-injection-mixin"; 9 | 10 | const SECTION_VIEW_MIXINS = { 11 | components: { 12 | AddRowControl, // Add Row 13 | draggable, // For Sorting Row/Control 14 | AddControlControl, // Add Control 15 | ControlView // Show Control 16 | }, 17 | 18 | mixins: [STYLE_INJECTION_MIXIN], 19 | 20 | props: { 21 | section: Object, 22 | rows: Object, 23 | controls: Object, 24 | permissions: Object 25 | }, 26 | 27 | data: () => ({ 28 | 29 | }), 30 | 31 | methods: { 32 | /** 33 | * Add a new rows for the section 34 | * @desc Typically, we create a new Row Object, push it into the global `Rows` Object. 35 | * And then assign the ID into the current section.rows 36 | * This method will be invoked whenever `AddRowControl` is emitted any value. 37 | * @emitKey addRowNotify 38 | */ 39 | addRow($event = null, extendData = null) { 40 | // get rowType of Section 41 | const rowType = SECTION_TYPES[this.section.type].rowType; 42 | 43 | // Create new Row Object - BUSS: New Object 44 | const newRowObject = createNewRow(rowType, extendData); 45 | 46 | // Parent-Handle: Add Row | Push ID into Section.rows 47 | this.$formEvent.$emit(EVENT_CONSTANTS.BUILDER.ROW.CREATE, newRowObject) 48 | this.$formEvent.$emit(EVENT_CONSTANTS.BUILDER.SECTION.ADDED_ROW, this.section.uniqueId, newRowObject.uniqueId) 49 | }, 50 | 51 | /** 52 | * Delete a row in the section 53 | * @desc Emit the rowId to the root-parent to handle the delete process 54 | * @param {Object} rowObject 55 | * @param {Object} sectionObj 56 | */ 57 | deleteRow(rowObject, sectionObj) { 58 | this.$formEvent.$emit( 59 | EVENT_CONSTANTS.BUILDER.ROW.DELETE, 60 | rowObject.uniqueId, 61 | sectionObj.uniqueId 62 | ) 63 | } 64 | }, 65 | 66 | computed: { 67 | /** 68 | * Property that will be used to drag - for Control Only 69 | */ 70 | dragControlHandle() { 71 | return ".option-control.drag-item" 72 | }, 73 | 74 | /** 75 | * Base group of drag/drop 76 | * We can share this for each section/row 77 | */ 78 | dragGroup() { 79 | return "v-form-builder-control" 80 | }, 81 | 82 | 83 | /** 84 | * Base group of drag/drop 85 | * We can share this for each section/row 86 | */ 87 | rowDragGroup() { 88 | return "v-form-builder-control-row-section".concat(this.section.uniqueId) 89 | }, 90 | 91 | /** 92 | * Accessor helper to check if the current section has control(s) or not 93 | */ 94 | hasControl() { 95 | return this.section.controls.length > 0 96 | }, 97 | 98 | /** 99 | * Classes for draggable 100 | * @returns {(string|string)[]} 101 | */ 102 | draggableClasses() { 103 | return [ 104 | this.styles.ROW, 105 | 'control-list-container', 106 | this.hasControl ? '' : 'empty' 107 | ] 108 | }, 109 | }, 110 | }; 111 | 112 | export { 113 | SECTION_VIEW_MIXINS 114 | } -------------------------------------------------------------------------------- /src/mixins/sidebar-body-mixin.js: -------------------------------------------------------------------------------- 1 | import {ALERT_DIALOG} from "@/libraries/alert-dialog"; 2 | 3 | const SIDEBAR_BODY_MIXIN = { 4 | props: { 5 | /** 6 | * Data Object that you will use for the Component inner Sidebar body 7 | * Normally, you need to create your own `data` field and emit it later. 8 | */ 9 | dataPackage: Object, 10 | 11 | /** 12 | * Main Form-Data from the Parent. 13 | * There might be some Configuration need these data. 14 | */ 15 | formData: Object 16 | }, 17 | 18 | data: () => ({ 19 | /** 20 | * Ideally, you have put override this key. It will be used to access your data to send it back to your component 21 | * @example For SidebarFormConfiguration. I created `formConfiguration` data field. And I will handle all data-changing stuff 22 | * only in `formConfiguration`. And that will be the data I need to send it back to the right component. 23 | * @required 24 | */ 25 | dataKey: "", 26 | }), 27 | 28 | methods: { 29 | /** 30 | * Close the sidebar without fire any events 31 | */ 32 | close() { 33 | this.$emit(this.emitCloseKey, false) 34 | }, 35 | 36 | /** 37 | * Save the configuration (Actually I will close the sidebar and emit event =)) ) 38 | */ 39 | save(close = false) { 40 | let data = this[this.dataKey] 41 | 42 | // pre-validation? 43 | if (this.preSaveValidation) { 44 | const validationErrorMessage = this.preSaveValidation(); 45 | 46 | // if has validation error => show error text and stop the submit process 47 | if (validationErrorMessage) { 48 | ALERT_DIALOG.show(validationErrorMessage); 49 | return; 50 | } 51 | } 52 | 53 | if (close) { 54 | this.$emit(this.emitSaveAndCloseKey, data) 55 | } else { 56 | this.$emit(this.emitSaveKey, data) 57 | } 58 | }, 59 | }, 60 | 61 | computed: { 62 | /** 63 | * Emit-Key to parent to close the sidebar 64 | * @returns {string} 65 | */ 66 | emitCloseKey() { 67 | return 'close' 68 | }, 69 | 70 | /** 71 | * Emit-Key to the parent to save the result 72 | * @returns {string} 73 | */ 74 | emitSaveKey() { 75 | return 'save' 76 | }, 77 | 78 | /** 79 | * Emit-Key - Save and Close 80 | * @returns {string} 81 | */ 82 | emitSaveAndCloseKey() { 83 | return 'saveAndClose' 84 | } 85 | } 86 | } 87 | 88 | export { 89 | SIDEBAR_BODY_MIXIN 90 | } -------------------------------------------------------------------------------- /src/mixins/style-injection-mixin.js: -------------------------------------------------------------------------------- 1 | import {STYLES} from "@/configs/styles"; 2 | 3 | 4 | const STYLE_INJECTION_MIXIN = { 5 | computed: { 6 | styles: () => STYLES 7 | }, 8 | }; 9 | 10 | export { 11 | STYLE_INJECTION_MIXIN 12 | } -------------------------------------------------------------------------------- /src/mixins/toggleable-mixin.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Toggleable Mixin - Same Methods/Properties would be use for Toggleable container 3 | * 4 | */ 5 | 6 | const TOGGLEABLE_MIXIN = { 7 | data:() => ({ 8 | isVisible: true, 9 | }), 10 | 11 | computed: { 12 | iconColor: () => '#000', 13 | iconSize: () => '32px', 14 | 15 | iconClose() { 16 | return this.$form.getIcon('chevronUp', this.iconSize, this.iconSize, this.iconColor) 17 | }, 18 | iconOpen() { 19 | return this.$form.getIcon('chevronDown', this.iconSize, this.iconSize, this.iconColor) 20 | } 21 | } 22 | } 23 | 24 | export { 25 | TOGGLEABLE_MIXIN 26 | } -------------------------------------------------------------------------------- /src/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue' 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /src/skeletons/README.md: -------------------------------------------------------------------------------- 1 | ## Skeletons of Vue Form Builder 2 | 3 | Basically, this folder contains all the skeletons (Base Classes) to let 4 | us create our own: 5 | - Control 6 | - Section (maybe) -------------------------------------------------------------------------------- /src/skeletons/controls/BaseControlConfigSkeleton.js: -------------------------------------------------------------------------------- 1 | import {CONTROL_SPECIAL_CONFIG_MIXIN} from "@/mixins/control-special-config-mixin"; 2 | 3 | export default { 4 | name: "BaseControlConfigSkeleton", 5 | mixins: [CONTROL_SPECIAL_CONFIG_MIXIN], 6 | } -------------------------------------------------------------------------------- /src/skeletons/controls/BaseControlSkeleton.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Control Skeleton 3 | * To extend for creating new custom control 4 | */ 5 | 6 | import {CONTROL_FIELD_EXTEND_MIXIN} from "@/mixins/control-field-extend-mixin"; 7 | 8 | export default { 9 | name: "BaseControlSkeleton", 10 | mixins: [CONTROL_FIELD_EXTEND_MIXIN], 11 | } 12 | -------------------------------------------------------------------------------- /src/views/README.md: -------------------------------------------------------------------------------- 1 | # Vue-Form-Builder: Views 2 | 3 | This folder contains all of the View Component for the: 4 | - FormBuilder 5 | - FormRenderer 6 | 7 | Why? Because I need to split it up as small as possible. Might be 8 | hard for newcomer to understand. But in the long run, this is the best way we can maintain. 9 | 10 | Also, me, think this is a good strategy. And easy to learn too. Keep 1 component in 1 Scope only. Nothing more, nothing less. 11 | 12 | ## Structural 13 | - builder: Different parts to build the `FormBuilder` 14 | - section-views: Section View Template (Normal, Toggleable, Table,...) 15 | - row-views: Row View Template (Table Row) 16 | - add-controls: Controls that help us to Add Section/Row/Control 17 | - sidebar-config-views: View for GlobalSidebar 18 | - control-views: Related View for the Control 19 | - controls: Control Item (Final Render) in here 20 | 21 | ## Flows 22 | - FormBuilder 23 | - FormConfiguration 24 | - SectionContainer 25 | - SectionView (`section-views`) 26 | - Row? (Normal/Toggle don't have rows) (`row-views`) 27 | - ControlView 28 | - ControlView 29 | - GlobalSidebar 30 | 31 | 32 | < Phat Tran > -------------------------------------------------------------------------------- /src/views/builder/FormConfiguration.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 93 | -------------------------------------------------------------------------------- /src/views/builder/GlobalKeyValueItemConfiguration.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | -------------------------------------------------------------------------------- /src/views/builder/GlobalModal.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 149 | -------------------------------------------------------------------------------- /src/views/builder/GlobalSidebar.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 137 | -------------------------------------------------------------------------------- /src/views/builder/SectionContainer.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 54 | 55 | -------------------------------------------------------------------------------- /src/views/builder/add-controls/AddControlControl.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /src/views/builder/add-controls/AddControlToRowControl.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /src/views/builder/add-controls/AddRowControl.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 30 | 31 | -------------------------------------------------------------------------------- /src/views/builder/add-controls/AddSectionControl.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 46 | -------------------------------------------------------------------------------- /src/views/builder/control-views/ControlLabel.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 31 | 32 | 37 | -------------------------------------------------------------------------------- /src/views/builder/control-views/ControlOption.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | -------------------------------------------------------------------------------- /src/views/builder/misc/IconTooltip.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 25 | 26 | -------------------------------------------------------------------------------- /src/views/builder/modal-config-views/TabRowConfigurationView.vue: -------------------------------------------------------------------------------- 1 | 26 | -------------------------------------------------------------------------------- /src/views/builder/row-views/TableRowView.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 24 | 25 | -------------------------------------------------------------------------------- /src/views/builder/section-navigation-buttons/TabSectionPreButtons.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /src/views/builder/section-views/NormalSectionView.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 57 | -------------------------------------------------------------------------------- /src/views/builder/section-views/TableSectionView.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 41 | 42 | -------------------------------------------------------------------------------- /src/views/builder/section-views/ToggleableSectionView.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | -------------------------------------------------------------------------------- /src/views/builder/sidebar-config-views/SidebarControlConfiguration.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | -------------------------------------------------------------------------------- /src/views/builder/sidebar-config-views/SidebarControlSelectList.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 60 | 61 | -------------------------------------------------------------------------------- /src/views/builder/sidebar-config-views/SidebarFormConfiguration.vue: -------------------------------------------------------------------------------- 1 | 77 | 78 | -------------------------------------------------------------------------------- /src/views/builder/sidebar-config-views/SidebarSectionConfiguration.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | -------------------------------------------------------------------------------- /src/views/builder/sidebar-config-views/control-configuration-views/ControlBasicInformation.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 56 | 57 | -------------------------------------------------------------------------------- /src/views/builder/sidebar-config-views/control-configuration-views/ControlStylingInformation.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 50 | 51 | -------------------------------------------------------------------------------- /src/views/builder/sidebar-config-views/control-configuration-views/ControlValidationInformation.vue: -------------------------------------------------------------------------------- 1 | 69 | 70 | -------------------------------------------------------------------------------- /src/views/container-views/SidebarToggleableContainer.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | -------------------------------------------------------------------------------- /src/views/control-configs/ButtonConfigView.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | -------------------------------------------------------------------------------- /src/views/control-configs/DatePickerConfigView.vue: -------------------------------------------------------------------------------- 1 | 92 | 93 | -------------------------------------------------------------------------------- /src/views/control-configs/DropdownConfigView.vue: -------------------------------------------------------------------------------- 1 | 103 | 104 | -------------------------------------------------------------------------------- /src/views/control-configs/FileUploaderConfigView.vue: -------------------------------------------------------------------------------- 1 | 66 | 67 | -------------------------------------------------------------------------------- /src/views/control-configs/InputConfigView.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | -------------------------------------------------------------------------------- /src/views/control-configs/LabelConfigView.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | -------------------------------------------------------------------------------- /src/views/control-configs/NumberConfigView.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | -------------------------------------------------------------------------------- /src/views/control-configs/README.md: -------------------------------------------------------------------------------- 1 | # Control Configuration View of Vue-Form-Builder 2 | 3 | Each control has some specific configuration field(s). So to render the configuration fields, we need to create a Vue component for that control here. 4 | -------------------------------------------------------------------------------- /src/views/control-configs/RadioCheckboxConfigView.vue: -------------------------------------------------------------------------------- 1 | 67 | 68 | -------------------------------------------------------------------------------- /src/views/control-configs/TextBlockConfigView.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /src/views/control-configs/TextConfigView.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/controls/ButtonControl.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | -------------------------------------------------------------------------------- /src/views/controls/EmptyBlockControl.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | -------------------------------------------------------------------------------- /src/views/controls/FileUploaderControl.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | -------------------------------------------------------------------------------- /src/views/controls/InputControl.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | -------------------------------------------------------------------------------- /src/views/controls/LabelControl.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /src/views/controls/NumberControl.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 82 | 83 | -------------------------------------------------------------------------------- /src/views/controls/README.md: -------------------------------------------------------------------------------- 1 | # Controls of Vue-Form-Builder 2 | 3 | This folder contains all the controls using for the Builder/Renderer. -------------------------------------------------------------------------------- /src/views/controls/TextBlockControl.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | -------------------------------------------------------------------------------- /src/views/controls/TextControl.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | -------------------------------------------------------------------------------- /src/views/renderer/ControlView.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 115 | -------------------------------------------------------------------------------- /src/views/renderer/SectionContainer.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 38 | 39 | -------------------------------------------------------------------------------- /src/views/renderer/row-views/TabContentRowView.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /src/views/renderer/section-views/NormalSectionView.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 41 | -------------------------------------------------------------------------------- /src/views/renderer/section-views/TabSectionView.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | -------------------------------------------------------------------------------- /src/views/renderer/section-views/TableSectionView.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 41 | 42 | -------------------------------------------------------------------------------- /src/views/renderer/section-views/ToggleableSectionView.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | -------------------------------------------------------------------------------- /static.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "dist", 3 | "clean_urls": true, 4 | "routes": { 5 | "/**": "index.html" 6 | } 7 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "webpack-env" 17 | ], 18 | "paths": { 19 | "@/*": [ 20 | "src/*" 21 | ] 22 | }, 23 | "lib": [ 24 | "esnext", 25 | "dom", 26 | "dom.iterable", 27 | "scripthost" 28 | ] 29 | }, 30 | "include": [ 31 | "src/**/*.ts", 32 | "src/**/*.tsx", 33 | "src/**/*.vue", 34 | "tests/**/*.ts", 35 | "tests/**/*.tsx" 36 | ], 37 | "exclude": [ 38 | "node_modules" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | productionSourceMap: false, 3 | css: { 4 | extract: true 5 | } 6 | } --------------------------------------------------------------------------------