├── .eslintrc.json ├── .gitignore ├── .husky └── pre-commit ├── .ncurc.js ├── .prettierignore ├── .prettierrc.json ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CHANGELOG.md ├── README.md ├── __mocks__ ├── fileMock.js └── styleMock.js ├── babel.config.js ├── dist ├── angular-material-form-builder.css ├── angular-material-form-builder.css.map ├── angular-material-form-builder.js ├── angular-material-form-builder.js.map ├── angular-material-form-builder.min.css ├── angular-material-form-builder.min.css.map ├── angular-material-form-builder.min.js └── angular-material-form-builder.min.js.map ├── favicon.ico ├── index.html ├── jest.config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── src ├── .eslintrc.json ├── lib │ ├── directives │ │ ├── agreement-item │ │ │ ├── agreement-item.controller.js │ │ │ ├── agreement-item.directive.js │ │ │ ├── agreement-item.tpl.html │ │ │ ├── agreement-view.controller.js │ │ │ ├── agreement-view.directive.js │ │ │ └── agreement-view.tpl.html │ │ ├── checkboxes-item │ │ │ ├── checkboxes-item.controller.js │ │ │ ├── checkboxes-item.directive.js │ │ │ ├── checkboxes-item.tpl.html │ │ │ ├── checkboxes-view.controller.js │ │ │ ├── checkboxes-view.directive.js │ │ │ └── checkboxes-view.tpl.html │ │ ├── form-item │ │ │ ├── form-item.controller.js │ │ │ ├── form-item.directive.js │ │ │ └── form-item.tpl.html │ │ ├── form-items-container │ │ │ ├── form-items-container.controller.js │ │ │ ├── form-items-container.directive.js │ │ │ └── form-items-container.tpl.html │ │ ├── form-view │ │ │ ├── form-view.controller.js │ │ │ ├── form-view.directive.js │ │ │ └── form-view.tpl.html │ │ ├── input-item │ │ │ ├── input-item.controller.js │ │ │ ├── input-item.directive.js │ │ │ ├── input-item.tpl.html │ │ │ ├── input-view.controller.js │ │ │ ├── input-view.directive.js │ │ │ └── input-view.tpl.html │ │ ├── label-item │ │ │ ├── label-item.controller.js │ │ │ ├── label-item.directive.js │ │ │ ├── label-item.tpl.html │ │ │ ├── label-view.controller.js │ │ │ ├── label-view.directive.js │ │ │ ├── label-view.tpl.html │ │ │ └── label.test.js │ │ ├── matrix-item │ │ │ ├── matrix-item.controller.js │ │ │ ├── matrix-item.directive.js │ │ │ ├── matrix-item.tpl.html │ │ │ ├── matrix-view.controller.js │ │ │ ├── matrix-view.directive.js │ │ │ └── matrix-view.tpl.html │ │ ├── radio-button-item │ │ │ ├── radio-button-item.controller.js │ │ │ ├── radio-button-item.directive.js │ │ │ ├── radio-button-item.tpl.html │ │ │ ├── radio-button-view.controller.js │ │ │ ├── radio-button-view.directive.js │ │ │ └── radio-button-view.tpl.html │ │ ├── select-item │ │ │ ├── select-item.controller.js │ │ │ ├── select-item.directive.js │ │ │ ├── select-item.tpl.html │ │ │ ├── select-view.controller.js │ │ │ ├── select-view.directive.js │ │ │ └── select-view.tpl.html │ │ ├── textarea-item │ │ │ ├── textarea-item.controller.js │ │ │ ├── textarea-item.directive.js │ │ │ ├── textarea-item.tpl.html │ │ │ ├── textarea-view.controller.js │ │ │ ├── textarea-view.directive.js │ │ │ └── textarea-view.tpl.html │ │ └── upload-item │ │ │ ├── upload-item.controller.js │ │ │ ├── upload-item.directive.js │ │ │ ├── upload-item.tpl.html │ │ │ ├── upload-view.controller.js │ │ │ ├── upload-view.directive.js │ │ │ └── upload-view.tpl.html │ ├── index.module.js │ ├── index.scss │ ├── main │ │ ├── main.controller.js │ │ └── main.controller.test.js │ └── utils │ │ ├── utils.service.js │ │ └── utils.service.test.js └── setup-jest.js └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended"], 3 | "plugins": ["node"], 4 | "root": true, 5 | "ignorePatterns": [ 6 | "chat/app", 7 | "public", 8 | "*.html", 9 | "*.scss", 10 | "*.css", 11 | "*.json", 12 | "*.spec.js" 13 | ], 14 | "parserOptions": { 15 | "sourceType": "module", 16 | "ecmaVersion": 2020 17 | }, 18 | // root env (node scripts, including webpack) 19 | "env": { 20 | "es6": true, 21 | "browser": false, 22 | "node": true, 23 | "commonjs": true, 24 | "jquery": false 25 | }, 26 | // rules for syntax errors 27 | "rules": { 28 | "no-var": "error", 29 | "prefer-const": [ 30 | "error", 31 | { "destructuring": "any", "ignoreReadBeforeAssign": false } 32 | ], 33 | "no-multi-assign": "error", 34 | "one-var": ["error", "never"], 35 | "quotes": [ 36 | "error", 37 | "single", 38 | { "avoidEscape": true, "allowTemplateLiterals": true } 39 | ], 40 | // console is OK for node/webpack so far 41 | "no-console": "off" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | .sass-cache/ 4 | .idea/ 5 | .tmp/ 6 | .DS_Store 7 | *_OLD_* 8 | coverage -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.ncurc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the config file for npm-check-updates 3 | * @see https://github.com/raineorshine/npm-check-updates#configuration-files 4 | */ 5 | module.exports = { 6 | upgrade: true, 7 | // Following packages CANNOT BE UPGRADED 8 | // because of broken semver, dependency issue or obsolescence 9 | reject: [], 10 | } 11 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | public/dist -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "dbaeumer.vscode-eslint", 4 | "esbenp.prettier-vscode", 5 | "eamodio.gitlens", 6 | "mikestead.dotenv", 7 | "dawhite.mustache", 8 | "DigitalBrainstem.javascript-ejs-support", 9 | "george-alisson.html-preview-vscode", 10 | "ms-vscode-remote.vscode-remote-extensionpack", 11 | "msjsdiag.debugger-for-chrome", 12 | "fabiospampinato.vscode-todo-plus", 13 | "PKief.material-icon-theme", 14 | "Equinusocio.vsc-community-material-theme", 15 | "gizak.shortcuts", 16 | "naumovs.color-highlight", 17 | "DotJoshJohnson.xml", 18 | "redhat.vscode-yaml", 19 | "ryu1kn.partial-diff", 20 | "christian-kohler.npm-intellisense" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | // Debug Jest tests 8 | { 9 | "name": "🧪 Debug Jest", 10 | "type": "node", 11 | "request": "launch", 12 | "runtimeExecutable": "node", 13 | "args": [ 14 | "node_modules/.bin/jest", 15 | "test", 16 | "--runInBand", 17 | "--detectOpenHandles", 18 | "--forceExit", 19 | "--testTimeout=6000000" 20 | ], 21 | "skipFiles": ["/**"] 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": ["src"], 3 | "editor.formatOnSave": true, 4 | "editor.defaultFormatter": "esbenp.prettier-vscode", 5 | "[jsonc]": { 6 | "editor.defaultFormatter": "esbenp.prettier-vscode" 7 | }, 8 | "[mdx]": { 9 | "editor.defaultFormatter": "esbenp.prettier-vscode" 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "esbenp.prettier-vscode" 13 | }, 14 | "[javascript]": { 15 | "editor.defaultFormatter": "esbenp.prettier-vscode" 16 | }, 17 | "[html]": { 18 | "editor.defaultFormatter": "esbenp.prettier-vscode" 19 | }, 20 | "[css]": { 21 | "editor.defaultFormatter": "esbenp.prettier-vscode" 22 | }, 23 | "[scss]": { 24 | "editor.defaultFormatter": "esbenp.prettier-vscode" 25 | }, 26 | "[sql]": { 27 | "editor.defaultFormatter": "mtxr.sqltools" 28 | }, 29 | "[typescriptreact]": { 30 | "editor.defaultFormatter": "esbenp.prettier-vscode" 31 | }, 32 | "[json]": { 33 | "editor.defaultFormatter": "esbenp.prettier-vscode" 34 | }, 35 | "[javascriptreact]": { 36 | "editor.defaultFormatter": "esbenp.prettier-vscode" 37 | }, 38 | "editor.tokenColorCustomizations": { 39 | "[Community Material Theme Darker High Contrast]": { 40 | "comments": "#5b9c78" 41 | } 42 | }, 43 | "files.associations": { 44 | "*.pgsql": "sql", 45 | "*.ejs": "html" 46 | }, 47 | "debug.javascript.terminalOptions": { 48 | "cwd": "${workspaceFolder}" 49 | }, 50 | "workbench.colorTheme": "Community Material Theme Darker High Contrast", 51 | "workbench.iconTheme": "material-icon-theme", 52 | "workbench.colorCustomizations": { 53 | "editor.selectionHighlightBackground": "#ffffff00", 54 | "editor.findMatchHighlightBackground": "#ffffff00", 55 | "editor.findMatchBorder": "#00ffd5", 56 | "editor.findMatchHighlightBorder": "#ff0000", 57 | "editor.selectionHighlightBorder": "#ffffff", 58 | "editor.lineHighlightBorder": "#272727", 59 | "editor.selectionBackground": "#eeff0044", 60 | "editor.wordHighlightBackground": "#eeff0044", 61 | "editorCursor.foreground": "#ffd900", 62 | "editor.lineHighlightBackground": "#181818", 63 | "editor.findMatchBackground": "#000000", 64 | "activityBar.activeBackground": "#daa9e9", 65 | "activityBar.activeBorder": "#987e27", 66 | "activityBar.background": "#daa9e9", 67 | "activityBar.foreground": "#15202b", 68 | "activityBar.inactiveForeground": "#15202b99", 69 | "activityBarBadge.background": "#987e27", 70 | "activityBarBadge.foreground": "#e7e7e7", 71 | "statusBar.background": "#c980df", 72 | "statusBar.foreground": "#15202b", 73 | "statusBarItem.hoverBackground": "#b857d5", 74 | "titleBar.activeBackground": "#c980df", 75 | "titleBar.activeForeground": "#15202b", 76 | "titleBar.inactiveBackground": "#c980df99", 77 | "titleBar.inactiveForeground": "#15202b99", 78 | "sash.hoverBorder": "#daa9e9", 79 | "statusBarItem.remoteBackground": "#c980df", 80 | "statusBarItem.remoteForeground": "#15202b" 81 | }, 82 | "peacock.remoteColor": "#c980df", 83 | "editor.guides.bracketPairs": true, 84 | "editor.bracketPairColorization.enabled": true 85 | } 86 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 1.2.0 2 | 3 | Label item can now render HTML strings for rich text formatting 4 | 5 | # 1.1.0 6 | 7 | Add support for the `upload` component that allows to upload files 8 | 9 | # 1.0.0 10 | 11 | ### ⚠️ Breaking changes 12 | 13 | No breaking changes in the API, but compatibility with AngularJS versions older than 1.8 has not been tested. 14 | 15 | ### ✨ Features and improvements 16 | 17 | - Support for AngularJS 1.8.x 18 | - +2 new items: 19 | - label: a simple label in your form 20 | - agreement: a statement with a checkbox 21 | - Full code refactoring using ES6+ 22 | - Use Webpack as build system 23 | - Use Jest for tests 24 | 25 | ### 🐞 Bug fixes 26 | 27 | - Minor bugfixes 28 | 29 | # 0.1.4 (bower) 30 | 31 | Support for AngularJS 1.6.9 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Material Form Builder! 2 | 3 | Now supporting Angular >= 1.8.2! 4 | 5 | This module enables you to easily build forms, just the way you do it in Google forms. 6 | The Module has two directives, one to create the form and the second to preview it: 7 | ![example1](http://i.imgur.com/i4e6KWQ.png) 8 | 9 | --- 10 | 11 | ## DEMO 12 | 13 | Run `npm start` 14 | 15 | It should open the browser on http://127.0.0.1:8080 16 | 17 | You can change host and port by setting the following env variables 18 | 19 | - `DEV_HOST` defaults to `127.0.0.1` 20 | - `DEV_PORT` defaults to `8080` 21 | 22 | ## Supported Form Items 23 | 24 | Here is the list of form items which are supported by the module: 25 | 26 | 1. Checkboxes (Group) 27 | 1. Radio Button (Group) 28 | 1. Plain input (Text, Number) 29 | 1. Textarea 30 | 1. Matrix 31 | 1. Select 32 | 1. Agreement Item 33 | 1. Label Item 34 | 1. Upload (manages input of type "file") 35 | 36 | ## Installation 37 | 38 | `npm install @xenialab/angular-material-form-builder` 39 | 40 | `npm install git+https://github.com/vlio20/angular-material-form-builder.git#v1.0.0` 41 | 42 | Add the following styles and scripts to your `index.html`: 43 | 44 | ```html 45 | 49 | 50 | ``` 51 | 52 | If you are using [wiredep](https://github.com/taptapship/wiredep) then just run in order to inject the module dependencies. 53 | 54 | ## Use 55 | 56 | In the form building step you need to use the `form-item` directive. Here is an example: 57 | 58 | ```html 59 | 60 | ``` 61 | 62 | This will produce the following form item: 63 | ![example2](http://i.imgur.com/6jOnwmu.png) 64 | **Note:** the _item_ attribute should receive an object `{}` and the _type_ attribute should receive one of the following: checkboxes, multipleChoices, input, textarea and matrix. 65 | 66 | In order to preview the form you will need to use the `form-view` directive: 67 | 68 | ```html 69 | 70 | ``` 71 | 72 | **Note:** the _form_ attribute should receive the following object: 73 | 74 | ```js 75 | { 76 | items: [{...}, {...}, ..., {...}] 77 | } 78 | ``` 79 | 80 | Each object in the `items` array should be the product of the `form-item` provided _item_ object. 81 | 82 | ## Also - Use 83 | 84 | You can also use `form-items-container` directive. This directives adds the option to handle movement and deletion of items in the list. You just need to pass it the form and it will make the rest for you. Here is a code example: 85 | 86 | ```html 87 | 88 | ``` 89 | 90 | _Action Attributes:_ 91 | there are also the following attributes: `on-delete`, `on-up`, `on-down`, if provided then the action will appear at the top right left corner of the item. This attribute expects callback function which will be executed after clicking on the action. If you will provide the index of the item (like in the example below) you will also receive it in your callback. 92 | Here is HTML example: 93 | 94 | ```html 95 | 104 | 105 | ``` 106 | 107 | JS example: 108 | 109 | ```js 110 | class MainController{ 111 | ... 112 | delete(item, index) { 113 | vm.form.items.splice(index, 1) 114 | } 115 | 116 | up(item, index) { 117 | if (index !== 0) { 118 | const prevItem = vm.form.items[index - 1] 119 | vm.form.items[index] = prevItem 120 | vm.form.items[index - 1] = item 121 | } 122 | } 123 | 124 | down(item, index) { 125 | if (index !== vm.form.items.length + 1) { 126 | const nextItem = vm.form.items[index + 1] 127 | vm.form.items[index] = nextItem 128 | vm.form.items[index + 1] = item 129 | } 130 | } 131 | ... 132 | } 133 | ``` 134 | 135 | Check the [MainController](src/lib/main/main.controller.js) implementation for full code. 136 | 137 | ## Contribution 138 | 139 | 1. Fork the repo 140 | 1. Run `npm i` to install all dependencies (including dev deps) 141 | 1. Run `npm start` in order to launch the live-reloading dev server 142 | 1. Ensure tests pass, then commit your changes to a new branch with a meaningful name and make a pull request 143 | 1. Report bugs and suggest enhancements. 144 | 145 | ### Building 146 | 147 | `npm run build` will make a new build (in the dist folder). 148 | 149 | ### Testing 150 | 151 | `npm test` will launch jest-based tests. They are run also automatically in VSCode but current coverage is modest (**Help wanted**). 152 | -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | console.log('Using babel configuration') 2 | module.exports = { 3 | presets: [ 4 | [ 5 | '@babel/preset-env', 6 | { 7 | targets: { 8 | browsers: ['last 1 years', 'not ie 11'], 9 | }, 10 | useBuiltIns: 'entry', 11 | corejs: 3, 12 | }, 13 | ], 14 | ], 15 | plugins: [['angularjs-annotate', { explicitOnly: false }]], 16 | } 17 | -------------------------------------------------------------------------------- /dist/angular-material-form-builder.css: -------------------------------------------------------------------------------- 1 | /*!*********************************************************************************************************************************************************************!*\ 2 | !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[0].use[2]!./src/lib/index.scss ***! 3 | \*********************************************************************************************************************************************************************/ 4 | @import url(https://fonts.googleapis.com/icon?family=Material+Icons); 5 | /*!*************************************************************************************************************************************************************************!*\ 6 | !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[0].use[2]!./src/lib/index.scss (1) ***! 7 | \*************************************************************************************************************************************************************************/ 8 | .main-view .content .builder, 9 | .main-view .content .view, 10 | .main-view .content .json { 11 | border-right: 1px solid #cccccc; 12 | height: 800px; 13 | overflow: auto; 14 | } 15 | 16 | .builder-icon, form-item .form-item-container .md-button, form-item .option-item .md-button { 17 | min-height: 36px; 18 | min-width: 36px; 19 | height: 36px; 20 | width: 36px; 21 | line-height: 0; 22 | } 23 | 24 | form-item .option-item { 25 | min-height: 75px; 26 | } 27 | form-item .option-item .md-button { 28 | line-height: 0; 29 | margin-top: 20px; 30 | } 31 | form-item .option-item .md-button.handle:active, form-item .option-item .md-button.handle:hover { 32 | cursor: move; 33 | } 34 | form-item .form-item-container { 35 | position: relative; 36 | padding-top: 30px; 37 | } 38 | form-item .form-item-container .form-item-actions { 39 | position: absolute; 40 | right: 20px; 41 | top: 0; 42 | } 43 | form-view .formItem-title { 44 | font-size: 18px; 45 | } 46 | form-view .formItem-help-text { 47 | font-size: 14px; 48 | color: #6c6c6c; 49 | } 50 | form-view .matrix-container { 51 | overflow: auto; 52 | } 53 | form-view .matrix-container .matrix .matrix-row { 54 | border-bottom: 1px solid #4caf50; 55 | } 56 | form-view .matrix-container .matrix .matrix-cell { 57 | text-align: center; 58 | overflow: hidden; 59 | } 60 | form-view .matrix-container .matrix md-radio-button .md-label { 61 | margin-left: 0; 62 | margin-right: 0; 63 | } 64 | form-view .matrix-container .matrix md-radio-button, 65 | form-view .matrix-container .matrix .md-switch-thumb { 66 | margin: 15px 0; 67 | } 68 | 69 | i.material-icons { 70 | position: absolute; 71 | top: 50%; 72 | left: 50%; 73 | transform: translate(-50%, -50%); 74 | } 75 | i.material-icons.medium { 76 | font-size: 36px; 77 | } 78 | 79 | .md-button.upload-button { 80 | padding: 0 16px; 81 | height: 36px; 82 | } 83 | .md-button.upload-button md-icon { 84 | font-size: 16px; 85 | } 86 | 87 | /*# sourceMappingURL=angular-material-form-builder.css.map*/ -------------------------------------------------------------------------------- /dist/angular-material-form-builder.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"angular-material-form-builder.css","mappings":";;;;;;;AAMI;;;EAGE;EACA;EACA;AAJN;;AASA;EACE,gBAfW;EAgBX,eAhBW;EAiBX,YAjBW;EAkBX,WAlBW;EAmBX;AANF;;AAUE;EACE;AAPJ;AASI;EAEE;EACA;AARN;AAUM;EAEE;AATR;AAcE;EACE;EACA;AAZJ;AAcI;EACE;EACA;EACA;AAZN;AAsBE;EACE;AApBJ;AA2BE;EACE;EACA;AAzBJ;AA4BE;EACE;AA1BJ;AA6BM;EACE;AA3BR;AA8BM;EACE;EACA;AA5BR;AAgCQ;EACE;EACA;AA9BV;AAkCM;;EAEE;AAhCR;;AAsCA;EACE;EACA;EACA;EACA;AAnCF;AAqCE;EACE;AAnCJ;;AAuCA;EACE;EACA;AApCF;AAqCE;EACE;AAnCJ,C","sources":["webpack://angular-material-form-builder/./src/lib/index.scss"],"sourcesContent":["@import url(https://fonts.googleapis.com/icon?family=Material+Icons);\n\n$button-dim: 36px;\n\n.main-view {\n .content {\n .builder,\n .view,\n .json {\n border-right: 1px solid #cccccc;\n height: 800px;\n overflow: auto;\n }\n }\n}\n\n.builder-icon {\n min-height: $button-dim;\n min-width: $button-dim;\n height: $button-dim;\n width: $button-dim;\n line-height: 0;\n}\n\nform-item {\n .option-item {\n min-height: 75px;\n\n .md-button {\n @extend .builder-icon;\n line-height: 0;\n margin-top: 20px;\n\n &.handle:active,\n &.handle:hover {\n cursor: move;\n }\n }\n }\n\n .form-item-container {\n position: relative;\n padding-top: 30px;\n\n .form-item-actions {\n position: absolute;\n right: 20px;\n top: 0;\n }\n\n .md-button {\n @extend .builder-icon;\n }\n }\n}\n\nform-view {\n .formItem-title {\n font-size: 18px;\n }\n\n // .formItem-content {\n // font-size: 18px;\n // }\n\n .formItem-help-text {\n font-size: 14px;\n color: #6c6c6c;\n }\n\n .matrix-container {\n overflow: auto;\n\n .matrix {\n .matrix-row {\n border-bottom: 1px solid #4caf50;\n }\n\n .matrix-cell {\n text-align: center;\n overflow: hidden;\n }\n\n md-radio-button {\n .md-label {\n margin-left: 0;\n margin-right: 0;\n }\n }\n\n md-radio-button,\n .md-switch-thumb {\n margin: 15px 0;\n }\n }\n }\n}\n\ni.material-icons {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n\n &.medium {\n font-size: 36px;\n }\n}\n\n.md-button.upload-button {\n padding: 0 16px;\n height: 36px;\n md-icon {\n font-size: 16px;\n }\n}\n"],"names":[],"sourceRoot":""} -------------------------------------------------------------------------------- /dist/angular-material-form-builder.min.css: -------------------------------------------------------------------------------- 1 | /*!*********************************************************************************************************************************************************************!*\ 2 | !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[0].use[2]!./src/lib/index.scss ***! 3 | \*********************************************************************************************************************************************************************/@import url(https://fonts.googleapis.com/icon?family=Material+Icons); 4 | /*!*************************************************************************************************************************************************************************!*\ 5 | !*** css ./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[0].use[1]!./node_modules/sass-loader/dist/cjs.js??ruleSet[1].rules[0].use[2]!./src/lib/index.scss (1) ***! 6 | \*************************************************************************************************************************************************************************/.main-view .content .builder,.main-view .content .json,.main-view .content .view{border-right:1px solid #ccc;height:800px;overflow:auto}.builder-icon,form-item .form-item-container .md-button,form-item .option-item .md-button{height:36px;line-height:0;min-height:36px;min-width:36px;width:36px}form-item .option-item{min-height:75px}form-item .option-item .md-button{line-height:0;margin-top:20px}form-item .option-item .md-button.handle:active,form-item .option-item .md-button.handle:hover{cursor:move}form-item .form-item-container{padding-top:30px;position:relative}form-item .form-item-container .form-item-actions{position:absolute;right:20px;top:0}form-view .formItem-title{font-size:18px}form-view .formItem-help-text{color:#6c6c6c;font-size:14px}form-view .matrix-container{overflow:auto}form-view .matrix-container .matrix .matrix-row{border-bottom:1px solid #4caf50}form-view .matrix-container .matrix .matrix-cell{overflow:hidden;text-align:center}form-view .matrix-container .matrix md-radio-button .md-label{margin-left:0;margin-right:0}form-view .matrix-container .matrix .md-switch-thumb,form-view .matrix-container .matrix md-radio-button{margin:15px 0}i.material-icons{left:50%;position:absolute;top:50%;transform:translate(-50%,-50%)}i.material-icons.medium{font-size:36px}.md-button.upload-button{height:36px;padding:0 16px}.md-button.upload-button md-icon{font-size:16px} 7 | /*# sourceMappingURL=angular-material-form-builder.min.css.map*/ -------------------------------------------------------------------------------- /dist/angular-material-form-builder.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"angular-material-form-builder.min.css","mappings":";;;;;6KAMI,iFAGE,2BAA+B,CAC/B,YAAa,CACb,aAJN,CASA,0FAGE,WAjBW,CAmBX,aAAc,CAJd,eAfW,CAgBX,cAhBW,CAkBX,UALF,CAUE,uBACE,eAPJ,CASI,kCAEE,aAAc,CACd,eARN,CAUM,+FAEE,WATR,CAcE,+BAEE,gBAAiB,CADjB,iBAXJ,CAcI,kDACE,iBAAkB,CAClB,UAAW,CACX,KAZN,CAsBE,0BACE,cApBJ,CA2BE,8BAEE,aAAc,CADd,cAxBJ,CA4BE,4BACE,aA1BJ,CA6BM,gDACE,+BA3BR,CA8BM,iDAEE,eAAgB,CADhB,iBA3BR,CAgCQ,8DACE,aAAc,CACd,cA9BV,CAkCM,yGAEE,aAhCR,CAsCA,iBAGE,QAAS,CAFT,iBAAkB,CAClB,OAAQ,CAER,8BAnCF,CAqCE,wBACE,cAnCJ,CAuCA,yBAEE,WAAY,CADZ,cAnCF,CAqCE,iCACE,cAnCJ","sources":["webpack://angular-material-form-builder/./src/lib/index.scss"],"sourcesContent":["@import url(https://fonts.googleapis.com/icon?family=Material+Icons);\n\n$button-dim: 36px;\n\n.main-view {\n .content {\n .builder,\n .view,\n .json {\n border-right: 1px solid #cccccc;\n height: 800px;\n overflow: auto;\n }\n }\n}\n\n.builder-icon {\n min-height: $button-dim;\n min-width: $button-dim;\n height: $button-dim;\n width: $button-dim;\n line-height: 0;\n}\n\nform-item {\n .option-item {\n min-height: 75px;\n\n .md-button {\n @extend .builder-icon;\n line-height: 0;\n margin-top: 20px;\n\n &.handle:active,\n &.handle:hover {\n cursor: move;\n }\n }\n }\n\n .form-item-container {\n position: relative;\n padding-top: 30px;\n\n .form-item-actions {\n position: absolute;\n right: 20px;\n top: 0;\n }\n\n .md-button {\n @extend .builder-icon;\n }\n }\n}\n\nform-view {\n .formItem-title {\n font-size: 18px;\n }\n\n // .formItem-content {\n // font-size: 18px;\n // }\n\n .formItem-help-text {\n font-size: 14px;\n color: #6c6c6c;\n }\n\n .matrix-container {\n overflow: auto;\n\n .matrix {\n .matrix-row {\n border-bottom: 1px solid #4caf50;\n }\n\n .matrix-cell {\n text-align: center;\n overflow: hidden;\n }\n\n md-radio-button {\n .md-label {\n margin-left: 0;\n margin-right: 0;\n }\n }\n\n md-radio-button,\n .md-switch-thumb {\n margin: 15px 0;\n }\n }\n }\n}\n\ni.material-icons {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n\n &.medium {\n font-size: 36px;\n }\n}\n\n.md-button.upload-button {\n padding: 0 16px;\n height: 36px;\n md-icon {\n font-size: 16px;\n }\n}\n"],"names":[],"sourceRoot":""} -------------------------------------------------------------------------------- /dist/angular-material-form-builder.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports["angular-material-form-builder"]=t():e["angular-material-form-builder"]=t()}(self,(function(){return(()=>{"use strict";var e={"./src/lib/directives/agreement-item/agreement-item.controller.js":(e,t,i)=>{function n(e,t){this.Element=t,this.item=e.extend(this.item||{},{config:{maxSelections:null},options:[{value:"",selected:!1}]})}i.r(t),i.d(t,{AgreementItemCtrl:()=>n}),n.$inject=["Utils","$element"]},"./src/lib/directives/agreement-item/agreement-item.directive.js":(e,t,i)=>{i.r(t),i.d(t,{AgreementItem:()=>o});var n=i("./src/lib/directives/agreement-item/agreement-item.controller.js");class o{constructor(){this.restrict="E",this.scope={item:"="},this.controller=n.AgreementItemCtrl,this.controllerAs="Agreement",this.bindToController=!0}}o.$inject=[]},"./src/lib/directives/agreement-item/agreement-view.controller.js":(e,t,i)=>{i.r(t),i.d(t,{AgreementViewCtrl:()=>n});class n{constructor(e,t){this.Scope=e,this.Utils=t,this.formItem={}}init(){this.formItem=this.Utils.extend(this.formItem||{},{config:{},options:[{value:"",selected:!1}]}),this.selectedOptions=this._getSelectedOptions(),this.disableOptions=!1,this.isValid=!0,this._updateView(),this._updateValidity(),this.isPreview()&&this._enableWatchers()}toggleSelectedOption(){this.selectedOptions=this._getSelectedOptions(),this._updateView(),this._updateValidity()}_getSelectedOptions(){return this.formItem.options.filter((e=>e.selected))}_updateView(){this.formItem.config.maxSelections&&this.selectedOptions.length===this.formItem.config.maxSelections?this.disableOptions=!0:this.disableOptions=!1}_updateValidity(){this.formItem.config.required?this.isValid=this.selectedOptions.length>0:this.isValid=!0,this.form.$setValidity("minSelections",this.isValid)}_enableWatchers(){this.Scope.$watch("AgreementView.formItem.config.required",(e=>{void 0!==e&&(this._updateView(),this._updateValidity())}))}}n.$inject=["$scope","Utils"]},"./src/lib/directives/agreement-item/agreement-view.directive.js":(e,t,i)=>{i.r(t),i.d(t,{AgreementView:()=>r});var n=i("./src/lib/directives/agreement-item/agreement-view.controller.js"),o=i("./src/lib/directives/agreement-item/agreement-view.tpl.html");class r{constructor(e){this.$timeout=e,this.template=o.default,this.restrict="E",this.scope={formItem:"=",isPreview:"&",form:"="},this.controller=n.AgreementViewCtrl,this.controllerAs="AgreementView",this.bindToController=!0}link(e,t,i,n){this.$timeout((function(){n.init()}),50)}}r.$inject=["$timeout"]},"./src/lib/directives/checkboxes-item/checkboxes-item.controller.js":(e,t,i)=>{i.r(t),i.d(t,{CheckboxesItemCtrl:()=>n});class n{constructor(e,t){this.Element=t,this.item=e.extend(this.item||{},{config:{maxSelections:null},options:[{value:"",selected:!1}]})}deleteOption(e){this.item.options.splice(e,1)}addOption(){this.item.options.push({value:"",selected:!1}),setTimeout((()=>{const e=this.Element.find("input");e[e.length-1].focus()}),0)}}n.$inject=["Utils","$element"]},"./src/lib/directives/checkboxes-item/checkboxes-item.directive.js":(e,t,i)=>{i.r(t),i.d(t,{CheckboxesItem:()=>r});var n=i("./src/lib/directives/checkboxes-item/checkboxes-item.tpl.html"),o=i("./src/lib/directives/checkboxes-item/checkboxes-item.controller.js");function r(){return{restrict:"E",template:n.default,scope:{item:"="},controller:o.CheckboxesItemCtrl,controllerAs:"Checkboxes",bindToController:!0}}},"./src/lib/directives/checkboxes-item/checkboxes-view.controller.js":(e,t,i)=>{i.r(t),i.d(t,{CheckboxesViewCtrl:()=>n});class n{constructor(e,t){this.Scope=e,this.Utils=t,this.formItem={}}init(){this.formItem=this.Utils.extend(this.formItem||{},{config:{},options:[]}),this.selectedOptions=this._getSelectedOptions(),this.disableOptions=!1,this.isValid=!0,this._updateView(),this._updateValidity(),this.isPreview()&&this._enableWatchers()}toggleSelectedOption(){this.selectedOptions=this._getSelectedOptions(),this._updateView(),this._updateValidity()}_getSelectedOptions(){return this.formItem.options.filter((e=>e.selected))}_updateView(){this.formItem.config.maxSelections&&this.selectedOptions.length===this.formItem.config.maxSelections?this.disableOptions=!0:this.disableOptions=!1}_updateValidity(){this.formItem.config.required?this.isValid=this.selectedOptions.length>0:this.isValid=!0,this.form.$setValidity("minSelections",this.isValid)}_enableWatchers(){this.Scope.$watch("CheckboxesView.formItem.config.required",(e=>{void 0!==e&&(this._updateView(),this._updateValidity())}))}}n.$inject=["$scope","Utils"]},"./src/lib/directives/checkboxes-item/checkboxes-view.directive.js":(e,t,i)=>{i.r(t),i.d(t,{CheckboxesView:()=>r});var n=i("./src/lib/directives/checkboxes-item/checkboxes-view.tpl.html"),o=i("./src/lib/directives/checkboxes-item/checkboxes-view.controller.js");class r{constructor(e){this.$timeout=e,this.restrict="E",this.template=n.default,this.scope={formItem:"=",isPreview:"&",form:"="},this.controller=o.CheckboxesViewCtrl,this.controllerAs="CheckboxesView",this.bindToController=!0}link(e,t,i,n){this.$timeout((function(){n.init()}),50)}}r.$inject=["$timeout"]},"./src/lib/directives/form-item/form-item.controller.js":(e,t,i)=>{i.r(t),i.d(t,{FormItemCtrl:()=>o});const n={upload:"Attachment",agreement:"Agreement",input:"Field",chooseFromList:"Select",label:"Label",multipleChoices:"Choice",matrix:"Matrix",checkboxes:"Options",textarea:"Text"};class o{constructor(e,t,i){this.Attrs=t,this.Utils=i,this.templates={upload:'',agreement:'',input:'',chooseFromList:'',label:'',multipleChoices:'',matrix:'',checkboxes:'',textarea:''},this.item={},this.scope=e}init(){this.item=this.Utils.extend(this.item||{},{type:this.Attrs.type,props:{title:n[this.Attrs.type],helpText:""},config:{required:!1}})}deleteClicked(){this.onDelete({item:this.item,index:this.index()})}_getItemTemplate(e){return'
delete arrow_drop_up arrow_drop_down
'+this.templates[e]+'Required field
'}}o.$inject=["$scope","$attrs","Utils"]},"./src/lib/directives/form-item/form-item.directive.js":(e,t,i)=>{i.r(t),i.d(t,{FormItem:()=>r});var n=i("./src/lib/directives/form-item/form-item.tpl.html"),o=i("./src/lib/directives/form-item/form-item.controller.js");class r{constructor(e){this.$compile=e,this.restrict="E",this.scope={item:"=",onDelete:"&",onUp:"&",onDown:"&",index:"&"},this.controller=o.FormItemCtrl,this.controllerAs="FormItem",this.bindToController=!0,this.template=n.default}link(e,t,i,n){const o=n._getItemTemplate(i.type),r=this.$compile(o)(e);return n.init(),r}}r.$inject=["$compile"]},"./src/lib/directives/form-items-container/form-items-container.controller.js":(e,t,i)=>{i.r(t),i.d(t,{FormItemsContainerCtrl:()=>n});class n{constructor(){this.form={items:[]}}delete(e,t){this.form.items.splice(t,1)}up(e,t){if(0!==t){const i=this.form.items[t-1];this.form.items[t]=i,this.form.items[t-1]=e}}down(e,t){if(t!==this.form.items.length-1){const i=this.form.items[t+1];this.form.items[t]=i,this.form.items[t+1]=e}}}n.$inject=[]},"./src/lib/directives/form-items-container/form-items-container.directive.js":(e,t,i)=>{i.r(t),i.d(t,{FormItemsContainer:()=>r});var n=i("./src/lib/directives/form-items-container/form-items-container.controller.js"),o=i("./src/lib/directives/form-items-container/form-items-container.tpl.html");class r{constructor(){this.restrict="E",this.scope={form:"="},this.template=o.default,this.controller=n.FormItemsContainerCtrl,this.controllerAs="container",this.bindToController=!0}}r.$inject=[]},"./src/lib/directives/form-view/form-view.controller.js":(e,t,i)=>{i.r(t),i.d(t,{FormViewCtrl:()=>n});class n{constructor(e){this.Scope=e}init(){}}n.$inject=["$scope"]},"./src/lib/directives/form-view/form-view.directive.js":(e,t,i)=>{i.r(t),i.d(t,{FormView:()=>r});var n=i("./src/lib/directives/form-view/form-view.controller.js"),o=i("./src/lib/directives/form-view/form-view.tpl.html");class r{constructor(){this.restrict="E",this.template=o.default,this.scope={form:"="},this.controller=n.FormViewCtrl,this.controllerAs="FormView",this.bindToController=!0}link(e,t,i,n){n.init()}}r.$inject=[]},"./src/lib/directives/input-item/input-item.controller.js":(e,t,i)=>{i.r(t),i.d(t,{InputItemCtrl:()=>n});class n{constructor(e,t){this.Element=t,this.item=e.extend(this.item||{},{config:{type:"text"}})}}n.$inject=["Utils","$element"]},"./src/lib/directives/input-item/input-item.directive.js":(e,t,i)=>{i.r(t),i.d(t,{InputItem:()=>r});var n=i("./src/lib/directives/input-item/input-item.controller.js"),o=i("./src/lib/directives/input-item/input-item.tpl.html");class r{constructor(){this.restrict="E",this.template=o.default,this.scope={item:"="},this.controller=n.InputItemCtrl,this.controllerAs="Input",this.bindToController=!0}}r.$inject=[]},"./src/lib/directives/input-item/input-view.controller.js":(e,t,i)=>{i.r(t),i.d(t,{InputViewCtrl:()=>n});class n{constructor(e){this.Utils=e,this.formItem={}}init(){this.Utils.extend(this.formItem,{config:{}})}}n.$inject=["Utils"]},"./src/lib/directives/input-item/input-view.directive.js":(e,t,i)=>{i.r(t),i.d(t,{InputView:()=>r});var n=i("./src/lib/directives/input-item/input-view.controller.js"),o=i("./src/lib/directives/input-item/input-view.tpl.html");class r{constructor(e){this.$timeout=e,this.restrict="E",this.template=o.default,this.scope={formItem:"=",form:"="},this.controller=n.InputViewCtrl,this.controllerAs="InputView",this.bindToController=!0}link(e,t,i,n){this.$timeout((function(){n.init()}),50)}}r.$inject=["$timeout"]},"./src/lib/directives/label-item/label-item.controller.js":(e,t,i)=>{i.r(t),i.d(t,{LabelItemCtrl:()=>n});class n{constructor(e){this.Element=e}}n.$inject=["$element"]},"./src/lib/directives/label-item/label-item.directive.js":(e,t,i)=>{i.r(t),i.d(t,{LabelItem:()=>r});var n=i("./src/lib/directives/label-item/label-item.controller.js"),o=i("./src/lib/directives/label-item/label-item.tpl.html");class r{constructor(){this.restrict="E",this.template=o.default,this.scope={item:"="},this.controller=n.LabelItemCtrl,this.controllerAs="Label",this.bindToController=!0}}},"./src/lib/directives/label-item/label-view.controller.js":(e,t,i)=>{i.r(t),i.d(t,{LabelViewCtrl:()=>n});class n{constructor(e,t){this.Utils=e,this.$sce=t}init(){this.Utils.extend(this.formItem,{})}get sanitizedTitle(){return this.$sce.trustAsHtml(this.formItem.value)}}n.$inject=["Utils","$sce"]},"./src/lib/directives/label-item/label-view.directive.js":(e,t,i)=>{i.r(t),i.d(t,{LabelView:()=>r});var n=i("./src/lib/directives/label-item/label-view.controller.js"),o=i("./src/lib/directives/label-item/label-view.tpl.html");class r{constructor(e){this.$timeout=e,this.restrict="E",this.template=o.default,this.scope={formItem:"=",form:"="},this.controller=n.LabelViewCtrl,this.controllerAs="LabelView",this.bindToController=!0}link(e,t,i,n){this.$timeout((function(){n.init()}),50)}}r.$inject=["$timeout"]},"./src/lib/directives/matrix-item/matrix-item.controller.js":(e,t,i)=>{i.r(t),i.d(t,{MatrixItemCtrl:()=>n});class n{constructor(e,t){this.RowContainer=angular.element(t[0].querySelector(".rowContainer")),this.ColumnContainer=angular.element(t[0].querySelector(".columnContainer")),this.item=e.extend(this.item||{},{config:{rows:[{value:""}],columns:[{value:""}]}})}deleteRow(e){this.item.config.rows.splice(e,1)}addRow(){this.item.config.rows.push({value:""}),setTimeout(function(){const e=this.RowContainer.find("input");e[e.length-1].focus()}.bind(this),0)}deleteColumn(e){this.item.config.columns.splice(e,1)}addColumn(){this.item.config.columns.push({value:""}),setTimeout((()=>{const e=this.ColumnContainer.find("input");e[e.length-1].focus()}),0)}}n.$inject=["Utils","$document"]},"./src/lib/directives/matrix-item/matrix-item.directive.js":(e,t,i)=>{i.r(t),i.d(t,{MatrixItem:()=>r});var n=i("./src/lib/directives/matrix-item/matrix-item.controller.js"),o=i("./src/lib/directives/matrix-item/matrix-item.tpl.html");class r{constructor(){this.restrict="E",this.template=o.default,this.scope={item:"="},this.controller=n.MatrixItemCtrl,this.controllerAs="Matrix",this.bindToController=!0}}r.$inject=[]},"./src/lib/directives/matrix-item/matrix-view.controller.js":(e,t,i)=>{i.r(t),i.d(t,{MatrixViewCtrl:()=>n});class n{constructor(e,t){this.Scope=e,this.Utils=t,this.isValid=!0,this.formItem={}}init(){this.formItem=this.Utils.extend(this.formItem,{config:{rows:[],columns:[]}}),this._updateValidity(),this.isPreview()&&this._enableWatchers()}_updateValidity(){let e=!0;this.formItem.config.required&&(e=!this.formItem.config.rows.some((e=>void 0===e.selected))),this.isValid=e,this.form.$setValidity("required",this.isValid)}_enableWatchers(){this.Scope.$watchGroup(["MatrixView.formItem.config.required","MatrixView.formItem.config.rows.length"],(e=>{void 0!==e&&this._updateValidity()}))}}n.$inject=["$scope","Utils"]},"./src/lib/directives/matrix-item/matrix-view.directive.js":(e,t,i)=>{i.r(t),i.d(t,{MatrixView:()=>r});var n=i("./src/lib/directives/matrix-item/matrix-view.tpl.html"),o=i("./src/lib/directives/matrix-item/matrix-view.controller.js");class r{constructor(e){this.$timeout=e,this.restrict="E",this.template=n.default,this.scope={formItem:"=",isPreview:"&",form:"="},this.controller=o.MatrixViewCtrl,this.controllerAs="MatrixView",this.bindToController=!0}link(e,t,i,n){this.$timeout((function(){n.init()}),50)}}r.$inject=["$timeout"]},"./src/lib/directives/radio-button-item/radio-button-item.controller.js":(e,t,i)=>{i.r(t),i.d(t,{RadioButtonItemCtrl:()=>n});class n{constructor(e,t){this.Element=t,this.item=e.extend(this.item||{},{config:{},options:[{value:""}]})}deleteOption(e){this.item.options.splice(e,1)}addOption(){this.item.options.push({value:""}),setTimeout((()=>{const e=this.Element.find("input");e[e.length-1].focus()}),0)}}n.$inject=["Utils","$element"]},"./src/lib/directives/radio-button-item/radio-button-item.directive.js":(e,t,i)=>{i.r(t),i.d(t,{RadioButtonItem:()=>r});var n=i("./src/lib/directives/radio-button-item/radio-button-item.controller.js"),o=i("./src/lib/directives/radio-button-item/radio-button-item.tpl.html");class r{constructor(){this.restrict="E",this.template=o.default,this.scope={item:"="},this.controller=n.RadioButtonItemCtrl,this.controllerAs="RadioButton",this.bindToController=!0}}r.$inject=[]},"./src/lib/directives/radio-button-item/radio-button-view.controller.js":(e,t,i)=>{i.r(t),i.d(t,{RadioButtonViewCtrl:()=>n});class n{constructor(e){this.Utils=e,this.formItem={}}init(){this.formItem=this.Utils.extend(this.formItem,{config:{},options:[]})}}n.$inject=["Utils"]},"./src/lib/directives/radio-button-item/radio-button-view.directive.js":(e,t,i)=>{i.r(t),i.d(t,{RadioButtonView:()=>r});var n=i("./src/lib/directives/radio-button-item/radio-button-view.controller.js"),o=i("./src/lib/directives/radio-button-item/radio-button-view.tpl.html");class r{constructor(e){this.$timeout=e,this.restrict="E",this.template=o.default,this.scope={formItem:"=",isPreview:"&",form:"="},this.controller=n.RadioButtonViewCtrl,this.controllerAs="RadioButtonView",this.bindToController=!0}link(e,t,i,n){this.$timeout((function(){n.init()}),50)}}r.$inject=["$timeout"]},"./src/lib/directives/select-item/select-item.controller.js":(e,t,i)=>{i.r(t),i.d(t,{SelectItemCtrl:()=>n});class n{constructor(e,t){this.Element=t,this.item=e.extend(this.item||{},{config:{},options:[{value:""}]})}deleteOption(e){this.item.options.splice(e,1)}addOption(){this.item.options.push({value:""}),setTimeout((()=>{const e=this.Element.find("input");e[e.length-1].focus()}),0)}}n.$inject=["Utils","$element"]},"./src/lib/directives/select-item/select-item.directive.js":(e,t,i)=>{i.r(t),i.d(t,{SelectItem:()=>r});var n=i("./src/lib/directives/select-item/select-item.tpl.html"),o=i("./src/lib/directives/select-item/select-item.controller.js");class r{constructor(){this.restrict="E",this.template=n.default,this.scope={item:"="},this.controller=o.SelectItemCtrl,this.controllerAs="Select",this.bindToController=!0}}r.$inject=[]},"./src/lib/directives/select-item/select-view.controller.js":(e,t,i)=>{i.r(t),i.d(t,{SelectViewCtrl:()=>n});class n{constructor(e){this.Utils=e,this.formItem={}}init(){this.formItem=this.Utils.extend(this.formItem,{config:{},options:[]})}}n.$inject=["Utils"]},"./src/lib/directives/select-item/select-view.directive.js":(e,t,i)=>{i.r(t),i.d(t,{SelectView:()=>r});var n=i("./src/lib/directives/select-item/select-view.tpl.html"),o=i("./src/lib/directives/select-item/select-view.controller.js");class r{constructor(e){this.$timeout=e,this.restrict="E",this.template=n.default,this.scope={formItem:"=",isPreview:"&",form:"="},this.controller=o.SelectViewCtrl,this.controllerAs="SelectView",this.bindToController=!0}link(e,t,i,n){this.$timeout((function(){n.init()}),50)}}r.$inject=["$timeout"]},"./src/lib/directives/textarea-item/textarea-item.controller.js":(e,t,i)=>{i.r(t),i.d(t,{TextareaItemCtrl:()=>n});class n{constructor(e,t){this.Element=t,this.item=e.extend(this.item||{},{config:{}})}}n.$inject=["Utils","$element"]},"./src/lib/directives/textarea-item/textarea-item.directive.js":(e,t,i)=>{i.r(t),i.d(t,{TextareaItem:()=>r});var n=i("./src/lib/directives/textarea-item/textarea-item.controller.js"),o=i("./src/lib/directives/textarea-item/textarea-item.tpl.html");class r{constructor(){this.restrict="E",this.template=o.default,this.scope={item:"="},this.controller=n.TextareaItemCtrl,this.controllerAs="Textarea",this.bindToController=!0}}r.$inject=[]},"./src/lib/directives/textarea-item/textarea-view.controller.js":(e,t,i)=>{i.r(t),i.d(t,{TextareaViewCtrl:()=>n});class n{constructor(e){this.Utils=e,this.formItem={}}init(){this.formItem=this.Utils.extend(this.formItem,{config:{}})}}n.$inject=["Utils"]},"./src/lib/directives/textarea-item/textarea-view.directive.js":(e,t,i)=>{i.r(t),i.d(t,{TextareaView:()=>r});var n=i("./src/lib/directives/textarea-item/textarea-view.controller.js"),o=i("./src/lib/directives/textarea-item/textarea-view.tpl.html");class r{constructor(e){this.$timeout=e,this.scope={formItem:"=",form:"="},this.restrict="E",this.template=o.default,this.controller=n.TextareaViewCtrl,this.controllerAs="TextareaView",this.bindToController=!0}link(e,t,i,n){this.$timeout((function(){n.init()}),50)}}r.$inject=["$timeout"]},"./src/lib/directives/upload-item/upload-item.controller.js":(e,t,i)=>{i.r(t),i.d(t,{UploadItemCtrl:()=>n});class n{constructor(e,t){this.Element=t,this.item=e.extend(this.item||{},{config:{},options:[]})}}n.$inject=["Utils","$element"]},"./src/lib/directives/upload-item/upload-item.directive.js":(e,t,i)=>{i.r(t),i.d(t,{UploadItem:()=>r});var n=i("./src/lib/directives/upload-item/upload-item.tpl.html"),o=i("./src/lib/directives/upload-item/upload-item.controller.js");class r{constructor(){this.restrict="E",this.template=n.default,this.scope={item:"="},this.controller=o.UploadItemCtrl,this.controllerAs="Upload",this.bindToController=!0}}r.$inject=[]},"./src/lib/directives/upload-item/upload-view.controller.js":(e,t,i)=>{i.r(t),i.d(t,{UploadViewCtrl:()=>n});class n{constructor(e,t,i){this.Scope=e,this.Element=i,this.Utils=t,this.formItem={}}init(){this.isMultiple=!1,this.showAllowed=!1,this.formItem=this.Utils.extend(this.formItem||{},{config:{size:10,uploadFileButtonLabel:"Add files"},options:[]}),this.isPreview()&&this._enableWatchers()}_updateMultiple(){this.isMultiple=!!this.formItem.config.multipleUpload;const e=angular.element(this.Element[0].querySelector("input[type=file]"));e&&(this.formItem.options=[],this.isMultiple?e.attr("multiple","multiple"):e.removeAttr("multiple"))}_updateAccept(){this.showAllowed=!!this.formItem.config.showAccept;const e=angular.element(this.Element[0].querySelector("input[type=file]"));e&&(this.showAllowed?e[0].setAttribute("accept",this.formItem.config.accept):(e[0].removeAttribute("accept"),delete this.formItem.config.accept))}_enableWatchers(){this.Scope.$watch("UploadView.formItem.config.multipleUpload",(e=>{void 0!==e&&this._updateMultiple()})),this.Scope.$watch("UploadView.formItem.config.showAccept",(e=>{void 0!==e&&this._updateAccept()})),this.Scope.$watch("UploadView.formItem.config.accept",(e=>{void 0!==e&&this._updateAccept()}))}removeItem(e){this.formItem.options.splice(e,1)}}n.$inject=["$scope","Utils","$element"]},"./src/lib/directives/upload-item/upload-view.directive.js":(e,t,i)=>{i.r(t),i.d(t,{UploadView:()=>r});var n=i("./src/lib/directives/upload-item/upload-view.controller.js"),o=i("./src/lib/directives/upload-item/upload-view.tpl.html");class r{constructor(e){this.$timeout=e,this.template=o.default,this.restrict="E",this.scope={formItem:"=",isPreview:"&",form:"="},this.controller=n.UploadViewCtrl,this.controllerAs="UploadView",this.bindToController=!0}link(e,t,i,n){this.$timeout((function(){n.init()}),50);const o=angular.element(t[0].querySelector(".upload-button")),r=angular.element(t[0].querySelector("input[type=file]")),s=angular.element(t[0].querySelector("label"));s.length&&s.css("display","none"),o.on("click",(()=>{s.css("display","none"),"function"==typeof r.trigger?r.trigger("click"):r[0].click()})),r.on("change",(t=>{e.$apply((function(){const e=Array.from(t.target.files),i=1048576*n.formItem.config.size;e.some((e=>e.size>=i))?(s.css("display","block"),s.text(n.formItem.config.sizeErrMessage),n.formItem.options=[]):n.formItem.options=e.map((e=>{const{name:t,size:i,type:n}=e;return{name:t,size:i,type:n,file:e}}))}))}))}}r.$inject=["$timeout"]},"./src/lib/main/main.controller.js":(e,t,i)=>{i.r(t),i.d(t,{MainController:()=>n});class n{constructor(){this.form={items:[]}}addItem(e){this.form.items.push({type:e})}delete(e,t){this.form.items.splice(t,1)}up(e,t){if(0!==t){const i=this.form.items[t-1];this.form.items[t]=i,this.form.items[t-1]=e}}down(e,t){if(t!==this.form.items.length-1){const i=this.form.items[t+1];this.form.items[t]=i,this.form.items[t+1]=e}}}n.$inject=[]},"./src/lib/utils/utils.service.js":(e,t,i)=>{i.r(t),i.d(t,{Utils:()=>n});class n{extend(e,t){return Object.keys(t).reduce(((e,i)=>(void 0===e[i]?e[i]=t[i]:"object"==typeof t[i]&&(e[i]=this.extend(e[i],t[i])),e)),void 0===e?{}:e)}}},"./src/lib/index.scss":(e,t,i)=>{i.r(t)},"./src/lib/directives/agreement-item/agreement-view.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n {{AgreementView.formItem.options[0].value}}\n \n\n
\n
\n Must select {{AgreementView.formItem.maxSelections || 1}} items\n
\n
\n
\n'},"./src/lib/directives/checkboxes-item/checkboxes-item.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n \n \n \n\n \n Layout direction ({{Checkboxes.item.config.direction == \'horizontal\' ?\n \'Horizontal\' : \'Vertical\'}})\n \n\n \n \n reorder\n \n\n \n \n \n \n\n \n delete\n \n \n
\n \n add\n \n
\n\n'},"./src/lib/directives/checkboxes-item/checkboxes-view.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n {{option.value}}\n \n\n
\n
\n Must select {{CheckboxesView.formItem.maxSelections || 1}} items\n
\n
\n
\n'},"./src/lib/directives/form-item/form-item.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='
\n
\n \n delete\n \n \n arrow_drop_up\n \n \n arrow_drop_down\n \n
\n\n \n \n \n \n\n \n \n \n \n\n \n \n \n \n\n
\n \n \n \n \n \n \n \n \n \n

UNKNOWN TYPE

\n
\n\n \n Required field\n \n
\n'},"./src/lib/directives/form-items-container/form-items-container.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='
\n \n \n
\n'},"./src/lib/directives/form-view/form-view.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='
\n \n \n
\n
{{formItem.props.title}}
\n
{{formItem.props.helpText}}
\n\n \n \n \n \n \n \n \n \n \n
\n
\n
\n\n'},"./src/lib/directives/input-item/input-item.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n \n\n\n \n \n Text\n Number\n Email\n \n\n'},"./src/lib/directives/input-item/input-view.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n
\n
This field is required
\n
\n
\n'},"./src/lib/directives/label-item/label-item.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n \n\n'},"./src/lib/directives/label-item/label-view.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n \n\n'},"./src/lib/directives/matrix-item/matrix-item.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n \n reorder\n \n\n \n \n \n \n\n \n delete\n Delete\n \n \n
\n Add Column\n
\n\n\n\n \n \n reorder\n \n\n \n \n \n \n\n \n delete\n Delete\n \n \n
\n Add row\n
\n\n'},"./src/lib/directives/matrix-item/matrix-view.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n
\n
\n \n {{column.value}}\n
\n \n {{row.value}}\n \n \n \n \n \n
\n \n\n
\n
This is required
\n
\n
\n'},"./src/lib/directives/radio-button-item/radio-button-item.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n Layout direction ({{RadioButton.item.config.direction == \'horizontal\' ?\n \'Horizontal\' : \'Vertical\'}})\n \n\n \n \n reorder\n \n\n \n \n \n \n\n \n delete\n \n \n\n Add Option\n\n'},"./src/lib/directives/radio-button-item/radio-button-view.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n \n {{option.value}}\n \n \n\n
\n
This field is required
\n
\n
\n'},"./src/lib/directives/select-item/select-item.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n \n reorder\n \n\n \n \n \n \n\n \n delete\n \n \n\n Add Option\n\n'},"./src/lib/directives/select-item/select-view.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n {{ option.value }}\n \n
\n
This field is required
\n
\n
\n'},"./src/lib/directives/textarea-item/textarea-item.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n \n\n'},"./src/lib/directives/textarea-item/textarea-view.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n
\n
This field is required
\n
\n
\n'},"./src/lib/directives/upload-item/upload-item.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='\n \n \n \n \n\n \n Multiple\n \n\n \n Max File size: {{Upload.item.config.size}} Mb\n \n \n\n \n \n \n \n\n \n Configure allowed file types\n \n\n \n \n \n \n\n'},"./src/lib/directives/upload-item/upload-view.tpl.html":(e,t,i)=>{i.r(t),i.d(t,{default:()=>n});const n='
\n \n \n \n \n \n \n\n attach_file{{UploadView.formItem.config.uploadFileButtonLabel}}\n
\n\n
\n \n \n \n close\n \n {{option.name}}\n
\n\n'}},t={};function i(n){var o=t[n];if(void 0!==o)return o.exports;var r=t[n]={exports:{}};return e[n](r,r.exports,i),r.exports}i.d=(e,t)=>{for(var n in t)i.o(t,n)&&!i.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),i.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var n={};return(()=>{i.r(n),i.d(n,{default:()=>$});i("./src/lib/index.scss");var e=i("./src/lib/directives/upload-item/upload-item.directive.js"),t=i("./src/lib/directives/upload-item/upload-view.directive.js"),o=i("./src/lib/directives/agreement-item/agreement-item.directive.js"),r=i("./src/lib/directives/agreement-item/agreement-view.directive.js"),s=i("./src/lib/main/main.controller.js"),l=i("./src/lib/utils/utils.service.js"),m=i("./src/lib/directives/checkboxes-item/checkboxes-item.directive.js"),c=i("./src/lib/directives/checkboxes-item/checkboxes-view.directive.js"),a=i("./src/lib/directives/form-item/form-item.directive.js"),d=i("./src/lib/directives/form-items-container/form-items-container.directive.js"),u=i("./src/lib/directives/form-view/form-view.directive.js"),p=i("./src/lib/directives/input-item/input-item.directive.js"),h=i("./src/lib/directives/input-item/input-view.directive.js"),b=i("./src/lib/directives/label-item/label-item.directive.js"),v=i("./src/lib/directives/label-item/label-view.directive.js"),f=i("./src/lib/directives/matrix-item/matrix-item.directive.js"),g=i("./src/lib/directives/matrix-item/matrix-view.directive.js"),w=i("./src/lib/directives/radio-button-item/radio-button-item.directive.js"),x=i("./src/lib/directives/radio-button-item/radio-button-view.directive.js"),I=i("./src/lib/directives/select-item/select-view.directive.js"),k=i("./src/lib/directives/select-item/select-item.directive.js"),V=i("./src/lib/directives/textarea-item/textarea-item.directive.js"),j=i("./src/lib/directives/textarea-item/textarea-view.directive.js");const $=angular.module("angularMaterialFormBuilder",["ngMaterial","angular-sortable-view","ngMessages"]).service("Utils",l.Utils).controller("MainController",s.MainController).directive("uploadItem",e.UploadItem).directive("uploadView",t.UploadView).directive("agreementItem",o.AgreementItem).directive("agreementView",r.AgreementView).directive("checkboxesItem",m.CheckboxesItem).directive("checkboxesView",c.CheckboxesView).directive("formItem",a.FormItem).directive("formItemsContainer",d.FormItemsContainer).directive("formView",u.FormView).directive("inputItem",p.InputItem).directive("inputView",h.InputView).directive("labelItem",b.LabelItem).directive("labelView",v.LabelView).directive("matrixItem",f.MatrixItem).directive("matrixView",g.MatrixView).directive("radioButtonItem",w.RadioButtonItem).directive("radioButtonView",x.RadioButtonView).directive("selectItem",k.SelectItem).directive("selectView",I.SelectView).directive("textareaItem",V.TextareaItem).directive("textareaView",j.TextareaView)})(),n})()})); 2 | //# sourceMappingURL=angular-material-form-builder.min.js.map -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vlio20/angular-material-form-builder/64713dad50ffaa13000e8dedefca6226cd4a9c46/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular Material Form Builder 6 | 10 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 27 | 31 | 32 | 33 | 34 | 41 | 42 |
43 |
44 |

Form Builder

45 |
46 | 55 | 56 |
57 |
58 | 59 |
60 |

Preview

61 |
62 | 63 |
64 |
65 | 66 |
67 |

Json

68 |
{{main.form | json}}
69 |
70 |
71 | 72 | 77 | 78 | 79 | add 80 | 81 | 82 | 83 | 84 | 89 | upload 90 | Upload 91 | 92 | 93 | 98 | pan_tool 99 | Agree Box 100 | 101 | 102 | 107 | label 108 | Label 109 | 110 | 111 | 116 | playlist_add_check 117 | Select 118 | 119 | 120 | 125 | grid_on 126 | Matrix 127 | 128 | 129 | 134 | keyboard 135 | Text-Area 136 | 137 | 138 | 143 | text_format 144 | Text-Input 145 | 146 | 147 | 152 | radio_button_checked 153 | Radio-Buttons 154 | 155 | 156 | 161 | check_box 162 | Checkboxes 163 | 164 | 165 | 166 | 167 | Fork me on GitHub 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Global setup 3 | * @see https://jestjs.io/docs/en/configuration#globalsetup-string 4 | * 5 | * Configuration and setup from angularjs-jest 6 | * @see https://github.com/dzikowski/angularjs-jest/tree/master/example 7 | * 8 | * @see https://github.com/jest-community/vscode-jest/issues/492 9 | * @type {import('@jest/types/build/Config').ProjectConfig} 10 | */ 11 | const config = { 12 | testEnvironment: 'jsdom', 13 | collectCoverage: false, 14 | coverageDirectory: './coverage', 15 | collectCoverageFrom: [ 16 | 'src/lib/**/*.js', 17 | '!**/node_modules/**', 18 | '!**/vendor/**', 19 | ], 20 | // For css imports 21 | moduleFileExtensions: ['js', 'html'], 22 | moduleNameMapper: { 23 | '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 24 | '/__mocks__/fileMock.js', 25 | '^.+\\.(css|less|scss)$': 'babel-jest', 26 | // '^.+\\.html?$': 'html-loader-jest', 27 | }, 28 | modulePathIgnorePatterns: ['/dist'], 29 | transform: { 30 | '^.+\\.js$': 'babel-jest', 31 | '^.+\\.html$': 'jest-raw-loader', 32 | }, 33 | setupFiles: ['./src/setup-jest.js'], 34 | } 35 | module.exports = config 36 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "module": "es2020", 5 | "moduleResolution": "classic", 6 | "lib": ["dom", "es2020", "webworker"], 7 | "allowSyntheticDefaultImports": true, 8 | "allowUmdGlobalAccess": true, 9 | "esModuleInterop": true, 10 | "isolatedModules": true, 11 | "baseUrl": "." 12 | }, 13 | "lib": ["es2020", "node", "jest"], 14 | "include": ["src", "test", "build-scripts", "test", "__mocks__"], 15 | "typeAcquisition": { 16 | "include": ["jest"] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@xenialab/angular-material-form-builder", 3 | "version": "1.2.2", 4 | "description": "Form builder for Angular Material (AngularJS version)", 5 | "main": "dist/angular-material-form-builder.min.js", 6 | "scripts": { 7 | "prebuild": "npm test", 8 | "build": "webpack --config webpack.config.js --mode development", 9 | "start": "webpack serve --mode development", 10 | "lint": "eslint src/", 11 | "lint-fix": "eslint src/ --fix", 12 | "prettify": "prettier --write src/", 13 | "test": "jest", 14 | "prepare": "husky install" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/vlio20/angular-material-form-builder.git" 19 | }, 20 | "keywords": [ 21 | "AngularJS", 22 | "angular-material", 23 | "form-builder" 24 | ], 25 | "author": "Vlad Ioffe", 26 | "contributors": [ 27 | "Giuseppe Careri ", 28 | "Daniele Cinti ", 29 | "Andrea Bianco ", 30 | "Vito Macchia ", 31 | "Marco Messina ", 32 | "Rodolfo Bevione " 33 | ], 34 | "engines": { 35 | "node": ">=14", 36 | "npm": ">=7" 37 | }, 38 | "files": [ 39 | "dist" 40 | ], 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/vlio20/angular-material-form-builder/issues" 44 | }, 45 | "homepage": "https://github.com/vlio20/angular-material-form-builder#readme", 46 | "peerDependencies": { 47 | "angular": "^1.8.2", 48 | "angular-animate": "^1.8.2", 49 | "angular-aria": "^1.8.2", 50 | "angular-material": ">=1.1.26 <=1.2.3", 51 | "angular-messages": "^1.8.2", 52 | "angular-sortable-view": ">=0.0.17", 53 | "mdi": "^2.2.43" 54 | }, 55 | "devDependencies": { 56 | "@babel/core": "^7.16.5", 57 | "@babel/preset-env": "^7.16.5", 58 | "@types/jest": "^27.0.3", 59 | "angular": "^1.8.2", 60 | "angular-animate": "^1.8.2", 61 | "angular-aria": "^1.8.2", 62 | "angular-material": "^1.2.3", 63 | "angular-messages": "^1.8.2", 64 | "angular-mocks": "^1.8.2", 65 | "angular-sortable-view": "0.0.21", 66 | "angularjs-jest": "^0.1.4", 67 | "babel-loader": "^8.2.3", 68 | "babel-plugin-angularjs-annotate": "^0.10.0", 69 | "clean-webpack-plugin": "^4.0.0", 70 | "css-loader": "^6.5.1", 71 | "css-minimizer-webpack-plugin": "^3.2.0", 72 | "eslint": "^8.4.1", 73 | "eslint-config-node": "^4.1.0", 74 | "eslint-plugin-import": "^2.25.3", 75 | "eslint-plugin-jest": "^25.3.0", 76 | "eslint-plugin-node": "^11.1.0", 77 | "eslint-webpack-plugin": "^3.1.1", 78 | "husky": "^7.0.4", 79 | "jest": "^27.4.5", 80 | "jest-raw-loader": "^1.0.1", 81 | "lint-staged": "^12.1.2", 82 | "mdi": "^2.2.43", 83 | "mini-css-extract-plugin": "^2.4.5", 84 | "prettier": "^2.5.1", 85 | "raw-loader": "^4.0.2", 86 | "sass": "^1.45.0", 87 | "sass-loader": "^12.4.0", 88 | "style-loader": "^3.3.1", 89 | "terser-webpack-plugin": "^5.2.5", 90 | "url-loader": "^4.1.1", 91 | "webpack": "^5.65.0", 92 | "webpack-cli": "^4.9.1", 93 | "webpack-dev-server": "^4.6.0" 94 | }, 95 | "lint-staged": { 96 | "./*.js": [ 97 | "prettier --write", 98 | "eslint" 99 | ], 100 | "src/**/*.*": [ 101 | "prettier --write" 102 | ], 103 | "src/**/*.js": [ 104 | "eslint" 105 | ] 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | // extends the root folder configuration with front-end specific env 3 | "extends": [ 4 | "../.eslintrc.json", 5 | "plugin:import/errors", 6 | "plugin:import/warnings" 7 | ], 8 | "plugins": ["import"], 9 | "root": true, 10 | "parserOptions": { 11 | "sourceType": "module", 12 | "ecmaVersion": 2020 13 | }, 14 | "env": { 15 | "es6": true, 16 | "browser": true, 17 | "node": false, 18 | "commonjs": false, 19 | "jquery": true 20 | }, 21 | "globals": { 22 | "PRODUCTION": "readonly", 23 | "VERSION": "readonly", 24 | "angular": "readonly" 25 | }, 26 | "rules": { 27 | "no-use-before-define": ["error", { "functions": true, "classes": true }], 28 | "no-console": "warn" 29 | }, 30 | "overrides": [ 31 | { 32 | "files": ["**/*.test.js"], 33 | "env": { "jest": true }, 34 | "plugins": ["jest"], 35 | "extends": ["plugin:jest/recommended", "../.eslintrc.json"], 36 | "settings": { 37 | "jest": { 38 | "version": 26 39 | } 40 | } 41 | } 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/directives/agreement-item/agreement-item.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngInject 3 | * 4 | * @param {import('../../utils/utils.service').Utils} Utils 5 | * @param {JQLite} $element 6 | */ 7 | function AgreementItemCtrl(Utils, $element) { 8 | this.Element = $element 9 | this.item = Utils.extend(this.item || {}, { 10 | config: { 11 | maxSelections: null, 12 | }, 13 | options: [ 14 | { 15 | value: '', 16 | selected: false, 17 | }, 18 | ], 19 | }) 20 | } 21 | 22 | export { AgreementItemCtrl } 23 | -------------------------------------------------------------------------------- /src/lib/directives/agreement-item/agreement-item.directive.js: -------------------------------------------------------------------------------- 1 | import { AgreementItemCtrl } from './agreement-item.controller' 2 | // import AgreementItemTemplate from './agreement-item.tpl.html' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class AgreementItem { 8 | /** 9 | * @ngInject 10 | */ 11 | constructor() { 12 | this.restrict = 'E' 13 | this.scope = { 14 | item: '=', 15 | } 16 | this.controller = AgreementItemCtrl 17 | this.controllerAs = 'Agreement' 18 | this.bindToController = true 19 | } 20 | } 21 | 22 | export { AgreementItem } 23 | -------------------------------------------------------------------------------- /src/lib/directives/agreement-item/agreement-item.tpl.html: -------------------------------------------------------------------------------- 1 |
7 | 13 |
14 | -------------------------------------------------------------------------------- /src/lib/directives/agreement-item/agreement-view.controller.js: -------------------------------------------------------------------------------- 1 | class AgreementViewCtrl { 2 | /** 3 | * @ngInject 4 | * @param {ng.IScope} $scope 5 | * @param {import('../../utils/utils.service').Utils} Utils 6 | */ 7 | constructor($scope, Utils) { 8 | this.Scope = $scope 9 | this.Utils = Utils 10 | this.formItem = {} 11 | } 12 | 13 | init() { 14 | this.formItem = this.Utils.extend(this.formItem || {}, { 15 | config: {}, 16 | options: [ 17 | { 18 | value: '', 19 | selected: false, 20 | }, 21 | ], 22 | }) 23 | 24 | this.selectedOptions = this._getSelectedOptions() 25 | this.disableOptions = false 26 | 27 | this.isValid = true 28 | this._updateView() 29 | this._updateValidity() 30 | if (this.isPreview()) { 31 | this._enableWatchers() 32 | } 33 | } 34 | 35 | toggleSelectedOption() { 36 | this.selectedOptions = this._getSelectedOptions() 37 | this._updateView() 38 | this._updateValidity() 39 | } 40 | 41 | _getSelectedOptions() { 42 | return this.formItem.options.filter((option) => { 43 | return option.selected 44 | }) 45 | } 46 | 47 | _updateView() { 48 | if (!this.formItem.config.maxSelections) { 49 | this.disableOptions = false 50 | } else if ( 51 | this.selectedOptions.length === this.formItem.config.maxSelections 52 | ) { 53 | this.disableOptions = true 54 | } else { 55 | this.disableOptions = false 56 | } 57 | } 58 | 59 | _updateValidity() { 60 | if (this.formItem.config.required) { 61 | this.isValid = this.selectedOptions.length > 0 62 | } else { 63 | this.isValid = true 64 | } 65 | 66 | this.form.$setValidity('minSelections', this.isValid) 67 | } 68 | 69 | _enableWatchers() { 70 | this.Scope.$watch('AgreementView.formItem.config.required', (newVal) => { 71 | if (newVal !== undefined) { 72 | this._updateView() 73 | this._updateValidity() 74 | } 75 | }) 76 | } 77 | } 78 | 79 | export { AgreementViewCtrl } 80 | -------------------------------------------------------------------------------- /src/lib/directives/agreement-item/agreement-view.directive.js: -------------------------------------------------------------------------------- 1 | import { AgreementViewCtrl } from './agreement-view.controller' 2 | import AgreementViewTemplate from './agreement-view.tpl.html' 3 | 4 | class AgreementView { 5 | /** 6 | * @ngInject 7 | * @param {ng.ITimeoutService} $timeout 8 | */ 9 | constructor($timeout) { 10 | this.$timeout = $timeout 11 | this.template = AgreementViewTemplate 12 | this.restrict = 'E' 13 | this.scope = { 14 | formItem: '=', 15 | isPreview: '&', 16 | form: '=', 17 | } 18 | this.controller = AgreementViewCtrl 19 | this.controllerAs = 'AgreementView' 20 | this.bindToController = true 21 | } 22 | 23 | /** 24 | * @see https://docs.angularjs.org/api/ng/service/$compile#-link- 25 | * @param {ng.IScope} scope - scope 26 | * @param {JQLite} element - element 27 | * @param {ng.IAttributes} attrs - attributes 28 | * @param {AgreementViewCtrl} ctrl - this instance controller 29 | * @param {ng.ITranscludeFunction} transcludeFn - transclude function ($transclude) 30 | */ 31 | link(scope, element, attrs, ctrl) { 32 | //this timeout is placed here in order to make sure that the creator directive of this view is finished its work 33 | this.$timeout(function () { 34 | ctrl.init() 35 | }, 50) 36 | } 37 | } 38 | 39 | export { AgreementView } 40 | -------------------------------------------------------------------------------- /src/lib/directives/agreement-item/agreement-view.tpl.html: -------------------------------------------------------------------------------- 1 | 2 |
5 | {{AgreementView.formItem.options[0].value}} 12 |
13 | 14 |
15 |
16 | Must select {{AgreementView.formItem.maxSelections || 1}} items 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /src/lib/directives/checkboxes-item/checkboxes-item.controller.js: -------------------------------------------------------------------------------- 1 | class CheckboxesItemCtrl { 2 | /** 3 | * @ngInject 4 | * 5 | * @param {import('../../utils/utils.service').Utils} Utils 6 | * @param {JQLite} $element 7 | */ 8 | constructor(Utils, $element) { 9 | this.Element = $element 10 | this.item = Utils.extend(this.item || {}, { 11 | config: { 12 | maxSelections: null, 13 | }, 14 | options: [ 15 | { 16 | value: '', 17 | selected: false, 18 | }, 19 | ], 20 | }) 21 | } 22 | 23 | deleteOption(index) { 24 | this.item.options.splice(index, 1) 25 | } 26 | 27 | addOption() { 28 | this.item.options.push({ 29 | value: '', 30 | selected: false, 31 | }) 32 | // Focus new element 33 | setTimeout(() => { 34 | const options = this.Element.find('input') 35 | const addedOption = options[options.length - 1] 36 | addedOption.focus() 37 | }, 0) 38 | } 39 | } 40 | 41 | export { CheckboxesItemCtrl } 42 | -------------------------------------------------------------------------------- /src/lib/directives/checkboxes-item/checkboxes-item.directive.js: -------------------------------------------------------------------------------- 1 | import CheckboxesItemTemplate from './checkboxes-item.tpl.html' 2 | import { CheckboxesItemCtrl } from './checkboxes-item.controller' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | function CheckboxesItem() { 8 | const directive = { 9 | restrict: 'E', 10 | template: CheckboxesItemTemplate, 11 | scope: { 12 | item: '=', 13 | }, 14 | controller: CheckboxesItemCtrl, 15 | controllerAs: 'Checkboxes', 16 | bindToController: true, 17 | } 18 | 19 | return directive 20 | } 21 | 22 | export { CheckboxesItem } 23 | -------------------------------------------------------------------------------- /src/lib/directives/checkboxes-item/checkboxes-item.tpl.html: -------------------------------------------------------------------------------- 1 |
7 | 8 | 9 | 10 | 11 | 12 | 17 | Layout direction ({{Checkboxes.item.config.direction == 'horizontal' ? 18 | 'Horizontal' : 'Vertical'}}) 19 | 20 | 21 |
27 | 33 | reorder 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | delete 43 | 44 |
45 |
46 | 50 | add 51 | 52 |
53 |
54 | -------------------------------------------------------------------------------- /src/lib/directives/checkboxes-item/checkboxes-view.controller.js: -------------------------------------------------------------------------------- 1 | class CheckboxesViewCtrl { 2 | /** 3 | * @ngInject 4 | * @param {ng.IScope} $scope 5 | * @param {import('../../utils/utils.service').Utils} Utils 6 | */ 7 | constructor($scope, Utils) { 8 | this.Scope = $scope 9 | this.Utils = Utils 10 | this.formItem = {} 11 | } 12 | 13 | init() { 14 | this.formItem = this.Utils.extend(this.formItem || {}, { 15 | config: {}, 16 | options: [], 17 | }) 18 | 19 | this.selectedOptions = this._getSelectedOptions() 20 | this.disableOptions = false 21 | 22 | this.isValid = true 23 | this._updateView() 24 | this._updateValidity() 25 | if (this.isPreview()) { 26 | this._enableWatchers() 27 | } 28 | } 29 | 30 | toggleSelectedOption() { 31 | this.selectedOptions = this._getSelectedOptions() 32 | this._updateView() 33 | this._updateValidity() 34 | } 35 | 36 | _getSelectedOptions() { 37 | return this.formItem.options.filter((option) => { 38 | return option.selected 39 | }) 40 | } 41 | 42 | _updateView() { 43 | if (!this.formItem.config.maxSelections) { 44 | this.disableOptions = false 45 | } else if ( 46 | this.selectedOptions.length === this.formItem.config.maxSelections 47 | ) { 48 | this.disableOptions = true 49 | } else { 50 | this.disableOptions = false 51 | } 52 | } 53 | 54 | _updateValidity() { 55 | if (this.formItem.config.required) { 56 | this.isValid = this.selectedOptions.length > 0 57 | } else { 58 | this.isValid = true 59 | } 60 | 61 | this.form.$setValidity('minSelections', this.isValid) 62 | } 63 | 64 | _enableWatchers() { 65 | this.Scope.$watch('CheckboxesView.formItem.config.required', (newVal) => { 66 | if (newVal !== undefined) { 67 | this._updateView() 68 | this._updateValidity() 69 | } 70 | }) 71 | } 72 | } 73 | 74 | export { CheckboxesViewCtrl } 75 | -------------------------------------------------------------------------------- /src/lib/directives/checkboxes-item/checkboxes-view.directive.js: -------------------------------------------------------------------------------- 1 | import CheckboxesViewTemplate from './checkboxes-view.tpl.html' 2 | import { CheckboxesViewCtrl } from './checkboxes-view.controller' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class CheckboxesView { 8 | /** 9 | * @ngInject 10 | * @param {ng.ITimeoutService} $timeout 11 | */ 12 | constructor($timeout) { 13 | this.$timeout = $timeout 14 | this.restrict = 'E' 15 | this.template = CheckboxesViewTemplate 16 | this.scope = { 17 | formItem: '=', 18 | isPreview: '&', 19 | form: '=', 20 | } 21 | this.controller = CheckboxesViewCtrl 22 | this.controllerAs = 'CheckboxesView' 23 | this.bindToController = true 24 | } 25 | 26 | /** 27 | * @see https://docs.angularjs.org/api/ng/service/$compile#-link- 28 | * @param {ng.IScope} scope - scope 29 | * @param {JQLite} element - element 30 | * @param {ng.IAttributes} attrs - attributes 31 | * @param {CheckboxesViewCtrl} ctrl - this instance controller 32 | * @param {ng.ITranscludeFunction} transcludeFn - transclude function ($transclude) 33 | */ 34 | link(scope, element, attrs, ctrl) { 35 | //this timeout is placed here in order to make sure that the creator directive of this view is finished its work 36 | this.$timeout(function () { 37 | ctrl.init() 38 | }, 50) 39 | } 40 | } 41 | 42 | export { CheckboxesView } 43 | -------------------------------------------------------------------------------- /src/lib/directives/checkboxes-item/checkboxes-view.tpl.html: -------------------------------------------------------------------------------- 1 | 2 |
5 | {{option.value}} 13 |
14 | 15 |
16 |
17 | Must select {{CheckboxesView.formItem.maxSelections || 1}} items 18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /src/lib/directives/form-item/form-item.controller.js: -------------------------------------------------------------------------------- 1 | const DEFAULT_TITLE = { 2 | upload: 'Attachment', 3 | agreement: 'Agreement', 4 | input: 'Field', 5 | chooseFromList: 'Select', 6 | label: 'Label', 7 | multipleChoices: 'Choice', 8 | matrix: 'Matrix', 9 | checkboxes: 'Options', 10 | textarea: 'Text', 11 | } 12 | 13 | class FormItemCtrl { 14 | /** 15 | * @ngInject 16 | * @param {ng.IScope} $scope 17 | * @param {ng.IAttributes} $attrs 18 | * @param {import('../../utils/utils.service').Utils} Utils 19 | */ 20 | constructor($scope, $attrs, Utils) { 21 | this.Attrs = $attrs 22 | this.Utils = Utils 23 | this.templates = { 24 | upload: '', 25 | agreement: '', 26 | input: '', 27 | chooseFromList: 28 | '', 29 | label: '', 30 | multipleChoices: 31 | '', 32 | matrix: '', 33 | checkboxes: '', 34 | textarea: '', 35 | } 36 | this.item = {} 37 | this.scope = $scope 38 | } 39 | 40 | init() { 41 | this.item = this.Utils.extend(this.item || {}, { 42 | type: this.Attrs.type, 43 | props: { 44 | title: DEFAULT_TITLE[this.Attrs.type], 45 | helpText: '', 46 | }, 47 | config: { 48 | required: false, 49 | }, 50 | }) 51 | } 52 | 53 | deleteClicked() { 54 | this.onDelete({ item: this.item, index: this.index() }) 55 | } 56 | 57 | /** 58 | * 59 | * @param {string} type 60 | */ 61 | _getItemTemplate(type) { 62 | const prefix = 63 | '' + 64 | '
' + 65 | '
' + 66 | ' ' + 67 | 'delete' + 68 | '' + 69 | ' ' + 70 | 'arrow_drop_up' + 71 | '' + 72 | ' ' + 73 | 'arrow_drop_down' + 74 | '' + 75 | '
' + 76 | '' + 77 | '' + 78 | '' + 79 | '' + 80 | '' + 81 | '' + 82 | '' + 83 | '' 84 | 85 | const suffix = 86 | '' + 87 | '' + 88 | 'Required field' + 89 | '' + 90 | '
' 91 | 92 | return prefix + this.templates[type] + suffix 93 | } 94 | } 95 | 96 | export { FormItemCtrl } 97 | -------------------------------------------------------------------------------- /src/lib/directives/form-item/form-item.directive.js: -------------------------------------------------------------------------------- 1 | import FormItemTemplate from './form-item.tpl.html' 2 | import { FormItemCtrl } from './form-item.controller' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class FormItem { 8 | /** 9 | * @ngInject 10 | * @param {ng.ICompileService} $compile 11 | */ 12 | constructor($compile) { 13 | this.$compile = $compile 14 | this.restrict = 'E' 15 | this.scope = { 16 | item: '=', 17 | onDelete: '&', 18 | onUp: '&', 19 | onDown: '&', 20 | index: '&', 21 | } 22 | this.controller = FormItemCtrl 23 | this.controllerAs = 'FormItem' 24 | this.bindToController = true 25 | this.template = FormItemTemplate 26 | } 27 | 28 | /** 29 | * @see https://docs.angularjs.org/api/ng/service/$compile#-link- 30 | * @param {ng.IScope} scope - scope 31 | * @param {JQLite} element - element 32 | * @param {ng.IAttributes} attrs - attributes 33 | * @param {FormItemCtrl} ctrl - this instance controller 34 | * @param {ng.ITranscludeFunction} transcludeFn - transclude function ($transclude) 35 | */ 36 | link(scope, element, attrs, ctrl) { 37 | const template = ctrl._getItemTemplate(attrs.type) 38 | const el = this.$compile(template)(scope) 39 | // element.append(el) 40 | // if done like above adds twice 41 | // element.append(this.$compile(template)(scope)) 42 | ctrl.init() 43 | return el 44 | } 45 | } 46 | export { FormItem } 47 | -------------------------------------------------------------------------------- /src/lib/directives/form-item/form-item.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 8 | delete 9 | 10 | 15 | arrow_drop_up 16 | 17 | 22 | arrow_drop_down 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 50 | 51 | 52 | 56 | 57 | 61 | 65 | 69 |

UNKNOWN TYPE

70 |
71 | 72 | 76 | Required field 79 | 80 |
81 | -------------------------------------------------------------------------------- /src/lib/directives/form-items-container/form-items-container.controller.js: -------------------------------------------------------------------------------- 1 | class FormItemsContainerCtrl { 2 | /** 3 | * @ngInject 4 | */ 5 | constructor() { 6 | /** 7 | * @type {import('../../main/main.controller').FormConfig} 8 | */ 9 | this.form = { 10 | items: [], 11 | } 12 | } 13 | 14 | /** 15 | * 16 | * @param {import('../../main/main.controller').Item} item 17 | * @param {number} index 18 | */ 19 | delete(item, index) { 20 | this.form.items.splice(index, 1) 21 | } 22 | 23 | /** 24 | * 25 | * @param {import('../../main/main.controller').Item} item 26 | * @param {number} index 27 | */ 28 | up(item, index) { 29 | if (index !== 0) { 30 | const prevItem = this.form.items[index - 1] 31 | this.form.items[index] = prevItem 32 | this.form.items[index - 1] = item 33 | } 34 | } 35 | 36 | /** 37 | * 38 | * @param {import('../../main/main.controller').Item} item 39 | * @param {number} index 40 | */ 41 | down(item, index) { 42 | if (index !== this.form.items.length - 1) { 43 | const nextItem = this.form.items[index + 1] 44 | this.form.items[index] = nextItem 45 | this.form.items[index + 1] = item 46 | } 47 | } 48 | } 49 | 50 | export { FormItemsContainerCtrl } 51 | -------------------------------------------------------------------------------- /src/lib/directives/form-items-container/form-items-container.directive.js: -------------------------------------------------------------------------------- 1 | import { FormItemsContainerCtrl } from './form-items-container.controller' 2 | import FormItemsContainerTemplate from './form-items-container.tpl.html' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class FormItemsContainer { 8 | /** 9 | * @ngInject 10 | */ 11 | constructor() { 12 | this.restrict = 'E' 13 | this.scope = { 14 | form: '=', 15 | } 16 | this.template = FormItemsContainerTemplate 17 | this.controller = FormItemsContainerCtrl 18 | this.controllerAs = 'container' 19 | this.bindToController = true 20 | } 21 | } 22 | 23 | export { FormItemsContainer } 24 | -------------------------------------------------------------------------------- /src/lib/directives/form-items-container/form-items-container.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /src/lib/directives/form-view/form-view.controller.js: -------------------------------------------------------------------------------- 1 | class FormViewCtrl { 2 | /** 3 | * @ngInject 4 | * @param {ng.IScù} $scope 5 | */ 6 | constructor($scope) { 7 | this.Scope = $scope 8 | } 9 | 10 | init() {} 11 | } 12 | 13 | export { FormViewCtrl } 14 | -------------------------------------------------------------------------------- /src/lib/directives/form-view/form-view.directive.js: -------------------------------------------------------------------------------- 1 | import { FormViewCtrl } from './form-view.controller' 2 | import FormViewTemplate from './form-view.tpl.html' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class FormView { 8 | /** 9 | * @ngInject 10 | */ 11 | constructor() { 12 | this.restrict = 'E' 13 | this.template = FormViewTemplate 14 | this.scope = { 15 | form: '=', 16 | } 17 | this.controller = FormViewCtrl 18 | this.controllerAs = 'FormView' 19 | this.bindToController = true 20 | } 21 | 22 | /** 23 | * @see https://docs.angularjs.org/api/ng/service/$compile#-link- 24 | * @param {ng.IScope} scope - scope 25 | * @param {JQLite} element - element 26 | * @param {ng.IAttributes} attrs - attributes 27 | * @param {FormViewCtrl} ctrl - this instance controller 28 | * @param {ng.ITranscludeFunction} transcludeFn - transclude function ($transclude) 29 | */ 30 | link(scope, element, attrs, ctrl) { 31 | ctrl.init() 32 | } 33 | } 34 | 35 | export { FormView } 36 | -------------------------------------------------------------------------------- /src/lib/directives/form-view/form-view.tpl.html: -------------------------------------------------------------------------------- 1 |
2 |
8 | 9 |
10 |
{{formItem.props.title}}
11 |
{{formItem.props.helpText}}
12 | 13 | 19 | 25 | 31 | 37 | 42 | 47 | 53 | 59 | 65 |
66 |
67 |
68 |
69 | -------------------------------------------------------------------------------- /src/lib/directives/input-item/input-item.controller.js: -------------------------------------------------------------------------------- 1 | class InputItemCtrl { 2 | /** 3 | * @ngInject 4 | * @param {import('../../utils/utils.service').Utils} Utils 5 | * @param {JQLite} $element 6 | */ 7 | constructor(Utils, $element) { 8 | this.Element = $element 9 | 10 | this.item = Utils.extend(this.item || {}, { 11 | config: { 12 | type: 'text', 13 | }, 14 | }) 15 | } 16 | } 17 | 18 | export { InputItemCtrl } 19 | -------------------------------------------------------------------------------- /src/lib/directives/input-item/input-item.directive.js: -------------------------------------------------------------------------------- 1 | import { InputItemCtrl } from './input-item.controller' 2 | import InputItemTemplate from './input-item.tpl.html' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class InputItem { 8 | /** 9 | * @ngInject 10 | */ 11 | constructor() { 12 | this.restrict = 'E' 13 | this.template = InputItemTemplate 14 | this.scope = { 15 | item: '=', 16 | } 17 | this.controller = InputItemCtrl 18 | this.controllerAs = 'Input' 19 | this.bindToController = true 20 | } 21 | } 22 | 23 | export { InputItem } 24 | -------------------------------------------------------------------------------- /src/lib/directives/input-item/input-item.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Text 9 | Number 10 | Email 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/lib/directives/input-item/input-view.controller.js: -------------------------------------------------------------------------------- 1 | class InputViewCtrl { 2 | /** 3 | * @ngInject 4 | * @param {import('../../utils/utils.service').Utils} Utils 5 | */ 6 | constructor(Utils) { 7 | this.Utils = Utils 8 | this.formItem = {} 9 | } 10 | 11 | init() { 12 | this.Utils.extend(this.formItem, { 13 | config: {}, 14 | }) 15 | } 16 | } 17 | 18 | export { InputViewCtrl } 19 | -------------------------------------------------------------------------------- /src/lib/directives/input-item/input-view.directive.js: -------------------------------------------------------------------------------- 1 | import { InputViewCtrl } from './input-view.controller' 2 | import InputViewTemplate from './input-view.tpl.html' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class InputView { 8 | /** 9 | * @ngInject 10 | * @param {ng.ITimeoutService} $timeout 11 | */ 12 | constructor($timeout) { 13 | this.$timeout = $timeout 14 | this.restrict = 'E' 15 | this.template = InputViewTemplate 16 | this.scope = { 17 | formItem: '=', 18 | form: '=', 19 | } 20 | this.controller = InputViewCtrl 21 | this.controllerAs = 'InputView' 22 | this.bindToController = true 23 | } 24 | 25 | /** 26 | * @see https://docs.angularjs.org/api/ng/service/$compile#-link- 27 | * @param {ng.IScope} scope - scope 28 | * @param {JQLite} element - element 29 | * @param {ng.IAttributes} attrs - attributes 30 | * @param {FormItemCtrl} ctrl - this instance controller 31 | * @param {ng.ITranscludeFunction} transcludeFn - transclude function ($transclude) 32 | */ 33 | link(scope, elem, attrs, ctrl) { 34 | //this timeout is placed here in order to make sure that the creator directive of this view is finished its work 35 | this.$timeout(function () { 36 | ctrl.init() 37 | }, 50) 38 | } 39 | } 40 | 41 | export { InputView } 42 | -------------------------------------------------------------------------------- /src/lib/directives/input-item/input-view.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 8 |
9 |
This field is required
10 |
11 |
12 | -------------------------------------------------------------------------------- /src/lib/directives/label-item/label-item.controller.js: -------------------------------------------------------------------------------- 1 | class LabelItemCtrl { 2 | /** 3 | * @ngInject 4 | * @param {JQLite} $element 5 | */ 6 | constructor($element) { 7 | this.Element = $element 8 | } 9 | } 10 | 11 | export { LabelItemCtrl } 12 | -------------------------------------------------------------------------------- /src/lib/directives/label-item/label-item.directive.js: -------------------------------------------------------------------------------- 1 | import { LabelItemCtrl } from './label-item.controller' 2 | import LabelItemTemplate from './label-item.tpl.html' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class LabelItem { 8 | constructor() { 9 | this.restrict = 'E' 10 | this.template = LabelItemTemplate 11 | this.scope = { 12 | item: '=', 13 | } 14 | this.controller = LabelItemCtrl 15 | this.controllerAs = 'Label' 16 | this.bindToController = true 17 | } 18 | } 19 | 20 | export { LabelItem } 21 | -------------------------------------------------------------------------------- /src/lib/directives/label-item/label-item.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/lib/directives/label-item/label-view.controller.js: -------------------------------------------------------------------------------- 1 | class LabelViewCtrl { 2 | /** 3 | * @ngInject 4 | * @param {import('../../utils/utils.service').Utils} Utils 5 | * @param {ng.ISCEService} $sce 6 | */ 7 | constructor(Utils, $sce) { 8 | this.Utils = Utils 9 | this.$sce = $sce 10 | } 11 | 12 | init() { 13 | this.Utils.extend(this.formItem, {}) 14 | } 15 | 16 | get sanitizedTitle() { 17 | return this.$sce.trustAsHtml(this.formItem.value) 18 | } 19 | } 20 | 21 | export { LabelViewCtrl } 22 | -------------------------------------------------------------------------------- /src/lib/directives/label-item/label-view.directive.js: -------------------------------------------------------------------------------- 1 | import { LabelViewCtrl } from './label-view.controller' 2 | import LabelViewTemplate from './label-view.tpl.html' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class LabelView { 8 | /** 9 | * @ngInject 10 | * @param {ng.ITimeoutService} $timeout 11 | */ 12 | constructor($timeout) { 13 | this.$timeout = $timeout 14 | this.restrict = 'E' 15 | this.template = LabelViewTemplate 16 | this.scope = { 17 | formItem: '=', 18 | form: '=', 19 | } 20 | this.controller = LabelViewCtrl 21 | this.controllerAs = 'LabelView' 22 | this.bindToController = true 23 | } 24 | 25 | /** 26 | * @see https://docs.angularjs.org/api/ng/service/$compile#-link- 27 | * @param {ng.IScope} scope - scope 28 | * @param {JQLite} element - element 29 | * @param {ng.IAttributes} attrs - attributes 30 | * @param {LabelViewCtrl} ctrl - this instance controller 31 | * @param {ng.ITranscludeFunction} transcludeFn - transclude function ($transclude) 32 | */ 33 | link(scope, elem, attrs, ctrl) { 34 | //this timeout is placed here in order to make sure that the creator directive of this view is finished its work 35 | this.$timeout(function () { 36 | ctrl.init() 37 | }, 50) 38 | } 39 | } 40 | 41 | export { LabelView } 42 | -------------------------------------------------------------------------------- /src/lib/directives/label-item/label-view.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/lib/directives/label-item/label.test.js: -------------------------------------------------------------------------------- 1 | // import angularMaterialFormBuilder from '../../index.module' 2 | import { createTestApp } from 'angularjs-jest' 3 | import 'angular-animate' 4 | import 'angular-sortable-view/src/angular-sortable-view' 5 | import 'angular-aria' 6 | import 'angular-messages' 7 | import 'angular-material' 8 | import '../../index.module' 9 | 10 | const formItem = { 11 | type: 'label', 12 | props: { title: '', helpText: '' }, 13 | config: { required: false }, 14 | value: 'Test Label', 15 | } 16 | 17 | describe('Label test', () => { 18 | const createTestAppWithLabel = () => 19 | createTestApp({ 20 | modules: ['angularMaterialFormBuilder'], 21 | mocks: {}, 22 | access: [], 23 | }) 24 | 25 | it('should render label', () => { 26 | const testApp = createTestAppWithLabel(() => {}) 27 | testApp.$scope.formItem = formItem 28 | 29 | const element = testApp.render( 30 | `` 31 | ) 32 | // const element = testApp.render(``) 33 | 34 | // await testApp.eventually(() => 35 | expect(element.html()).toContain('Test Label') 36 | // ) 37 | 38 | // expect(element.html()).toContain('Test Label') 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /src/lib/directives/matrix-item/matrix-item.controller.js: -------------------------------------------------------------------------------- 1 | class MatrixItemCtrl { 2 | /** 3 | * @ngInject 4 | * @param {import('../../utils/utils.service').Utils} Utils 5 | * @param {ng.IDocumentService} $document 6 | */ 7 | constructor(Utils, $document) { 8 | this.RowContainer = angular.element( 9 | $document[0].querySelector('.rowContainer') 10 | ) 11 | this.ColumnContainer = angular.element( 12 | $document[0].querySelector('.columnContainer') 13 | ) 14 | 15 | this.item = Utils.extend(this.item || {}, { 16 | config: { 17 | rows: [ 18 | { 19 | value: '', 20 | }, 21 | ], 22 | columns: [ 23 | { 24 | value: '', 25 | }, 26 | ], 27 | }, 28 | }) 29 | } 30 | 31 | /** 32 | * 33 | * @param {number} index 34 | */ 35 | deleteRow(index) { 36 | this.item.config.rows.splice(index, 1) 37 | } 38 | 39 | addRow() { 40 | this.item.config.rows.push({ 41 | value: '', 42 | }) 43 | 44 | setTimeout( 45 | function () { 46 | const options = this.RowContainer.find('input') 47 | const addedOption = options[options.length - 1] 48 | addedOption.focus() 49 | }.bind(this), 50 | 0 51 | ) 52 | } 53 | 54 | /** 55 | * 56 | * @param {number} index 57 | */ 58 | deleteColumn(index) { 59 | this.item.config.columns.splice(index, 1) 60 | } 61 | 62 | addColumn() { 63 | this.item.config.columns.push({ 64 | value: '', 65 | }) 66 | 67 | setTimeout(() => { 68 | const options = this.ColumnContainer.find('input') 69 | const addedOption = options[options.length - 1] 70 | addedOption.focus() 71 | }, 0) 72 | } 73 | } 74 | 75 | export { MatrixItemCtrl } 76 | -------------------------------------------------------------------------------- /src/lib/directives/matrix-item/matrix-item.directive.js: -------------------------------------------------------------------------------- 1 | import { MatrixItemCtrl } from './matrix-item.controller' 2 | import MatrixItemTemplate from './matrix-item.tpl.html' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class MatrixItem { 8 | /** 9 | * @ngInject 10 | */ 11 | constructor() { 12 | this.restrict = 'E' 13 | this.template = MatrixItemTemplate 14 | this.scope = { 15 | item: '=', 16 | } 17 | this.controller = MatrixItemCtrl 18 | this.controllerAs = 'Matrix' 19 | this.bindToController = true 20 | } 21 | } 22 | export { MatrixItem } 23 | -------------------------------------------------------------------------------- /src/lib/directives/matrix-item/matrix-item.tpl.html: -------------------------------------------------------------------------------- 1 |
7 |
13 | 19 | reorder 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | delete 34 | Delete 35 | 36 |
37 |
38 | Add Column 45 |
46 |
47 | 48 |
54 |
60 | 66 | reorder 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 80 | delete 81 | Delete 82 | 83 |
84 |
85 | Add row 92 |
93 |
94 | -------------------------------------------------------------------------------- /src/lib/directives/matrix-item/matrix-view.controller.js: -------------------------------------------------------------------------------- 1 | class MatrixViewCtrl { 2 | /** 3 | * @ngInject 4 | * @param {ng.IScope} $scope 5 | * @param {import('../../utils/utils.service').Utils} Utils 6 | */ 7 | constructor($scope, Utils) { 8 | this.Scope = $scope 9 | this.Utils = Utils 10 | this.isValid = true 11 | this.formItem = {} 12 | } 13 | 14 | init() { 15 | this.formItem = this.Utils.extend(this.formItem, { 16 | config: { 17 | rows: [], 18 | columns: [], 19 | }, 20 | }) 21 | 22 | this._updateValidity() 23 | if (this.isPreview()) { 24 | this._enableWatchers() 25 | } 26 | } 27 | 28 | _updateValidity() { 29 | let valid = true 30 | if (this.formItem.config.required) { 31 | valid = !this.formItem.config.rows.some( 32 | (row) => typeof row['selected'] === 'undefined' 33 | ) 34 | // for (let i = 0; i < this.formItem.config.rows.length; i++) { 35 | // if (typeof this.formItem.config.rows[i]['selected'] === 'undefined') { 36 | // valid = false 37 | // break 38 | // } 39 | // } 40 | } 41 | 42 | this.isValid = valid 43 | this.form.$setValidity('required', this.isValid) 44 | } 45 | 46 | _enableWatchers() { 47 | this.Scope.$watchGroup( 48 | [ 49 | 'MatrixView.formItem.config.required', 50 | 'MatrixView.formItem.config.rows.length', 51 | ], 52 | (newVal) => { 53 | if (newVal !== undefined) { 54 | this._updateValidity() 55 | } 56 | } 57 | ) 58 | } 59 | } 60 | export { MatrixViewCtrl } 61 | -------------------------------------------------------------------------------- /src/lib/directives/matrix-item/matrix-view.directive.js: -------------------------------------------------------------------------------- 1 | import MatrixViewTemplate from './matrix-view.tpl.html' 2 | import { MatrixViewCtrl } from './matrix-view.controller' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | 8 | class MatrixView { 9 | /** 10 | * @ngInject 11 | * @param {ng.ITimeoutService} $timeout 12 | */ 13 | constructor($timeout) { 14 | this.$timeout = $timeout 15 | this.restrict = 'E' 16 | this.template = MatrixViewTemplate 17 | this.scope = { 18 | formItem: '=', 19 | isPreview: '&', 20 | form: '=', 21 | } 22 | this.controller = MatrixViewCtrl 23 | this.controllerAs = 'MatrixView' 24 | this.bindToController = true 25 | } 26 | 27 | /** 28 | * @see https://docs.angularjs.org/api/ng/service/$compile#-link- 29 | * @param {ng.IScope} scope - scope 30 | * @param {JQLite} element - element 31 | * @param {ng.IAttributes} attrs - attributes 32 | * @param {MatrixViewCtrl} ctrl - this instance controller 33 | * @param {ng.ITranscludeFunction} transcludeFn - transclude function ($transclude) 34 | */ 35 | link(scope, elem, attrs, ctrl) { 36 | //this timeout is placed here in order to make sure that the creator directive of this view is finished its work 37 | this.$timeout(function () { 38 | ctrl.init() 39 | }, 50) 40 | } 41 | } 42 | 43 | export { MatrixView } 44 | -------------------------------------------------------------------------------- /src/lib/directives/matrix-item/matrix-view.tpl.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 5 | {{column.value}} 11 |
12 |
17 | {{row.value}} 20 | 26 | 31 | 35 | 36 | 37 |
38 |
39 | 40 |
41 |
This is required
42 |
43 |
44 | -------------------------------------------------------------------------------- /src/lib/directives/radio-button-item/radio-button-item.controller.js: -------------------------------------------------------------------------------- 1 | class RadioButtonItemCtrl { 2 | /** 3 | * @ngInject 4 | * @param {import('../../utils/utils.service').Utils} Utils 5 | * @param {JQLite} $element 6 | */ 7 | constructor(Utils, $element) { 8 | this.Element = $element 9 | this.item = Utils.extend(this.item || {}, { 10 | config: {}, 11 | options: [ 12 | { 13 | value: '', 14 | }, 15 | ], 16 | }) 17 | } 18 | 19 | deleteOption(index) { 20 | this.item.options.splice(index, 1) 21 | } 22 | 23 | addOption() { 24 | this.item.options.push({ 25 | value: '', 26 | }) 27 | 28 | setTimeout(() => { 29 | const options = this.Element.find('input') 30 | const addedOption = options[options.length - 1] 31 | addedOption.focus() 32 | }, 0) 33 | } 34 | } 35 | 36 | export { RadioButtonItemCtrl } 37 | -------------------------------------------------------------------------------- /src/lib/directives/radio-button-item/radio-button-item.directive.js: -------------------------------------------------------------------------------- 1 | import { RadioButtonItemCtrl } from './radio-button-item.controller' 2 | import RadioButtonTemplate from './radio-button-item.tpl.html' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | 8 | class RadioButtonItem { 9 | /** 10 | * @ngInject 11 | */ 12 | constructor() { 13 | this.restrict = 'E' 14 | this.template = RadioButtonTemplate 15 | this.scope = { 16 | item: '=', 17 | } 18 | this.controller = RadioButtonItemCtrl 19 | this.controllerAs = 'RadioButton' 20 | this.bindToController = true 21 | } 22 | } 23 | 24 | export { RadioButtonItem } 25 | -------------------------------------------------------------------------------- /src/lib/directives/radio-button-item/radio-button-item.tpl.html: -------------------------------------------------------------------------------- 1 |
7 | 12 | Layout direction ({{RadioButton.item.config.direction == 'horizontal' ? 13 | 'Horizontal' : 'Vertical'}}) 14 | 15 | 16 |
22 | 28 | reorder 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 40 | delete 41 | 42 |
43 | 44 | Add Option 47 |
48 | -------------------------------------------------------------------------------- /src/lib/directives/radio-button-item/radio-button-view.controller.js: -------------------------------------------------------------------------------- 1 | class RadioButtonViewCtrl { 2 | /** 3 | * @ngInject 4 | * @param {import('../../utils/utils.service').Utils} Utils 5 | */ 6 | constructor(Utils) { 7 | this.Utils = Utils 8 | this.formItem = {} 9 | } 10 | 11 | init() { 12 | this.formItem = this.Utils.extend(this.formItem, { 13 | config: {}, 14 | options: [], 15 | }) 16 | } 17 | } 18 | 19 | export { RadioButtonViewCtrl } 20 | -------------------------------------------------------------------------------- /src/lib/directives/radio-button-item/radio-button-view.directive.js: -------------------------------------------------------------------------------- 1 | import { RadioButtonViewCtrl } from './radio-button-view.controller' 2 | import RadioButtonViewTemplate from './radio-button-view.tpl.html' 3 | 4 | class RadioButtonView { 5 | /** 6 | * @ngInject 7 | * @param {ng.ITimeoutService} $timeout 8 | */ 9 | constructor($timeout) { 10 | this.$timeout = $timeout 11 | this.restrict = 'E' 12 | this.template = RadioButtonViewTemplate 13 | this.scope = { 14 | formItem: '=', 15 | isPreview: '&', 16 | form: '=', 17 | } 18 | this.controller = RadioButtonViewCtrl 19 | this.controllerAs = 'RadioButtonView' 20 | this.bindToController = true 21 | } 22 | 23 | /** 24 | * @see https://docs.angularjs.org/api/ng/service/$compile#-link- 25 | * @param {ng.IScope} scope - scope 26 | * @param {JQLite} element - element 27 | * @param {ng.IAttributes} attrs - attributes 28 | * @param {RadioButtonViewCtrl} ctrl - this instance controller 29 | * @param {ng.ITranscludeFunction} transcludeFn - transclude function ($transclude) 30 | */ 31 | link(scope, elem, attrs, ctrl) { 32 | //this timeout is placed here in order to make sure that the creator directive of this view is finished its work 33 | this.$timeout(function () { 34 | ctrl.init() 35 | }, 50) 36 | } 37 | } 38 | 39 | export { RadioButtonView } 40 | -------------------------------------------------------------------------------- /src/lib/directives/radio-button-item/radio-button-view.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 14 | {{option.value}} 15 | 16 | 17 | 18 |
19 |
This field is required
20 |
21 |
22 | -------------------------------------------------------------------------------- /src/lib/directives/select-item/select-item.controller.js: -------------------------------------------------------------------------------- 1 | class SelectItemCtrl { 2 | /** 3 | * @ngInject 4 | * @param {import('../../utils/utils.service').Utils} Utils 5 | * @param {JQLite} $element 6 | */ 7 | constructor(Utils, $element) { 8 | this.Element = $element 9 | this.item = Utils.extend(this.item || {}, { 10 | config: {}, 11 | options: [ 12 | { 13 | value: '', 14 | }, 15 | ], 16 | }) 17 | } 18 | 19 | /** 20 | * 21 | * @param {number} index 22 | */ 23 | deleteOption(index) { 24 | this.item.options.splice(index, 1) 25 | } 26 | 27 | addOption() { 28 | this.item.options.push({ 29 | value: '', 30 | }) 31 | 32 | setTimeout(() => { 33 | const options = this.Element.find('input') 34 | const addedOption = options[options.length - 1] 35 | addedOption.focus() 36 | }, 0) 37 | } 38 | } 39 | 40 | export { SelectItemCtrl } 41 | -------------------------------------------------------------------------------- /src/lib/directives/select-item/select-item.directive.js: -------------------------------------------------------------------------------- 1 | import SelectTemplate from './select-item.tpl.html' 2 | import { SelectItemCtrl } from './select-item.controller' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class SelectItem { 8 | /** 9 | * @ngInject 10 | */ 11 | constructor() { 12 | this.restrict = 'E' 13 | this.template = SelectTemplate 14 | this.scope = { 15 | item: '=', 16 | } 17 | this.controller = SelectItemCtrl 18 | this.controllerAs = 'Select' 19 | this.bindToController = true 20 | } 21 | } 22 | 23 | export { SelectItem } 24 | -------------------------------------------------------------------------------- /src/lib/directives/select-item/select-item.tpl.html: -------------------------------------------------------------------------------- 1 |
7 |
13 | 19 | reorder 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | delete 29 | 30 |
31 | 32 | Add Option 35 |
36 | -------------------------------------------------------------------------------- /src/lib/directives/select-item/select-view.controller.js: -------------------------------------------------------------------------------- 1 | class SelectViewCtrl { 2 | /** 3 | * @ngInject 4 | * @param {import('../../utils/utils.service').Utils} Utils 5 | */ 6 | constructor(Utils) { 7 | this.Utils = Utils 8 | this.formItem = {} 9 | } 10 | 11 | init() { 12 | this.formItem = this.Utils.extend(this.formItem, { 13 | config: {}, 14 | options: [], 15 | }) 16 | } 17 | } 18 | 19 | export { SelectViewCtrl } 20 | -------------------------------------------------------------------------------- /src/lib/directives/select-item/select-view.directive.js: -------------------------------------------------------------------------------- 1 | import SelectViewTemplate from './select-view.tpl.html' 2 | import { SelectViewCtrl } from './select-view.controller' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class SelectView { 8 | /** 9 | * @ngInject 10 | * @param {ng.ITimeoutService} $timeout 11 | */ 12 | constructor($timeout) { 13 | this.$timeout = $timeout 14 | 15 | this.restrict = 'E' 16 | this.template = SelectViewTemplate 17 | this.scope = { 18 | formItem: '=', 19 | isPreview: '&', 20 | form: '=', 21 | } 22 | this.controller = SelectViewCtrl 23 | this.controllerAs = 'SelectView' 24 | this.bindToController = true 25 | } 26 | 27 | /** 28 | * @see https://docs.angularjs.org/api/ng/service/$compile#-link- 29 | * @param {ng.IScope} scope - scope 30 | * @param {JQLite} element - element 31 | * @param {ng.IAttributes} attrs - attributes 32 | * @param {SelectViewCtrl} ctrl - this instance controller 33 | * @param {ng.ITranscludeFunction} transcludeFn - transclude function ($transclude) 34 | */ 35 | link(scope, elem, attrs, ctrl) { 36 | //this timeout is placed here in order to make sure that the creator directive of this view is finished its work 37 | this.$timeout(function () { 38 | ctrl.init() 39 | }, 50) 40 | } 41 | } 42 | 43 | export { SelectView } 44 | -------------------------------------------------------------------------------- /src/lib/directives/select-item/select-view.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | {{ option.value }} 12 | 13 |
14 |
This field is required
15 |
16 |
17 | -------------------------------------------------------------------------------- /src/lib/directives/textarea-item/textarea-item.controller.js: -------------------------------------------------------------------------------- 1 | class TextareaItemCtrl { 2 | /** 3 | * @ngInject 4 | * @param {import('../../utils/utils.service').Utils} Utils 5 | * @param {JQLite} $element 6 | */ 7 | constructor(Utils, $element) { 8 | this.Element = $element 9 | 10 | this.item = Utils.extend(this.item || {}, { 11 | config: {}, 12 | }) 13 | } 14 | } 15 | 16 | export { TextareaItemCtrl } 17 | -------------------------------------------------------------------------------- /src/lib/directives/textarea-item/textarea-item.directive.js: -------------------------------------------------------------------------------- 1 | import { TextareaItemCtrl } from './textarea-item.controller' 2 | import TextareaItemTemplate from './textarea-item.tpl.html' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class TextareaItem { 8 | /** 9 | * @ngInject 10 | */ 11 | constructor() { 12 | this.restrict = 'E' 13 | this.template = TextareaItemTemplate 14 | this.scope = { 15 | item: '=', 16 | } 17 | this.controller = TextareaItemCtrl 18 | this.controllerAs = 'Textarea' 19 | this.bindToController = true 20 | } 21 | } 22 | 23 | export { TextareaItem } 24 | -------------------------------------------------------------------------------- /src/lib/directives/textarea-item/textarea-item.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/lib/directives/textarea-item/textarea-view.controller.js: -------------------------------------------------------------------------------- 1 | class TextareaViewCtrl { 2 | /** 3 | * @ngInject 4 | * @param {import('../../utils/utils.service').Utils} Utils 5 | */ 6 | constructor(Utils) { 7 | this.Utils = Utils 8 | this.formItem = {} 9 | } 10 | 11 | init() { 12 | this.formItem = this.Utils.extend(this.formItem, { 13 | config: {}, 14 | }) 15 | } 16 | } 17 | 18 | export { TextareaViewCtrl } 19 | -------------------------------------------------------------------------------- /src/lib/directives/textarea-item/textarea-view.directive.js: -------------------------------------------------------------------------------- 1 | import { TextareaViewCtrl } from './textarea-view.controller' 2 | import TextareaViewTemplate from './textarea-view.tpl.html' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | 8 | class TextareaView { 9 | /** 10 | * @ngInject 11 | * @param {ng.ITimeoutService} $timeout 12 | */ 13 | constructor($timeout) { 14 | this.$timeout = $timeout 15 | this.scope = { 16 | formItem: '=', 17 | form: '=', 18 | } 19 | this.restrict = 'E' 20 | this.template = TextareaViewTemplate 21 | this.controller = TextareaViewCtrl 22 | this.controllerAs = 'TextareaView' 23 | this.bindToController = true 24 | } 25 | 26 | /** 27 | * @see https://docs.angularjs.org/api/ng/service/$compile#-link- 28 | * @param {ng.IScope} scope - scope 29 | * @param {JQLite} element - element 30 | * @param {ng.IAttributes} attrs - attributes 31 | * @param {TextareaViewCtrl} ctrl - this instance controller 32 | * @param {ng.ITranscludeFunction} transcludeFn - transclude function ($transclude) 33 | */ 34 | link(scope, elem, attrs, ctrl) { 35 | //this timeout is placed here in order to make sure that the creator directive of this view is finished its work 36 | this.$timeout(function () { 37 | ctrl.init() 38 | }, 50) 39 | } 40 | } 41 | 42 | export { TextareaView } 43 | -------------------------------------------------------------------------------- /src/lib/directives/textarea-item/textarea-view.tpl.html: -------------------------------------------------------------------------------- 1 | 2 | 7 |
8 |
This field is required
9 |
10 |
11 | -------------------------------------------------------------------------------- /src/lib/directives/upload-item/upload-item.controller.js: -------------------------------------------------------------------------------- 1 | class UploadItemCtrl { 2 | /** 3 | * @ngInject 4 | * 5 | * @param {import('../../utils/utils.service').Utils} Utils 6 | * @param {JQLite} $element 7 | */ 8 | constructor(Utils, $element) { 9 | this.Element = $element 10 | this.item = Utils.extend(this.item || {}, { 11 | config: {}, 12 | options: [], 13 | }) 14 | } 15 | } 16 | 17 | export { UploadItemCtrl } 18 | -------------------------------------------------------------------------------- /src/lib/directives/upload-item/upload-item.directive.js: -------------------------------------------------------------------------------- 1 | import UploadItemTemplate from './upload-item.tpl.html' 2 | import { UploadItemCtrl } from './upload-item.controller' 3 | 4 | /** 5 | * @implements {ng.IDirective} 6 | */ 7 | class UploadItem { 8 | /** 9 | * @ngInject 10 | */ 11 | constructor() { 12 | this.restrict = 'E' 13 | this.template = UploadItemTemplate 14 | this.scope = { 15 | item: '=', 16 | } 17 | this.controller = UploadItemCtrl 18 | this.controllerAs = 'Upload' 19 | this.bindToController = true 20 | } 21 | } 22 | 23 | export { UploadItem } 24 | -------------------------------------------------------------------------------- /src/lib/directives/upload-item/upload-item.tpl.html: -------------------------------------------------------------------------------- 1 |
7 | 8 | 9 | 10 | 11 | 12 | 17 | Multiple 18 | 19 | 20 | 21 | Max File size: {{Upload.item.config.size}} Mb 22 | 32 | 33 | 34 | 35 | 36 | 41 | 42 | 43 | 48 | Configure allowed file types 49 | 50 | 51 | 52 | 53 | 58 | 59 |
60 | -------------------------------------------------------------------------------- /src/lib/directives/upload-item/upload-view.controller.js: -------------------------------------------------------------------------------- 1 | class UploadViewCtrl { 2 | /** 3 | * @ngInject 4 | * @param {ng.IScope} $scope 5 | * @param {import('../../utils/utils.service').Utils} Utils 6 | * @param {JQLite} $element 7 | */ 8 | constructor($scope, Utils, $element) { 9 | this.Scope = $scope 10 | this.Element = $element 11 | this.Utils = Utils 12 | this.formItem = {} 13 | } 14 | init() { 15 | this.isMultiple = false 16 | this.showAllowed = false 17 | this.formItem = this.Utils.extend(this.formItem || {}, { 18 | config: { 19 | size: 10, 20 | uploadFileButtonLabel: 'Add files', 21 | }, 22 | options: [], 23 | }) 24 | if (this.isPreview()) { 25 | this._enableWatchers() 26 | } 27 | } 28 | _updateMultiple() { 29 | this.isMultiple = !!this.formItem.config.multipleUpload 30 | const input = angular.element( 31 | this.Element[0].querySelector('input[type=file]') 32 | ) 33 | if (input) { 34 | this.formItem.options = [] 35 | if (this.isMultiple) { 36 | input.attr('multiple', 'multiple') 37 | } else { 38 | input.removeAttr('multiple') 39 | } 40 | } 41 | } 42 | 43 | _updateAccept() { 44 | this.showAllowed = !!this.formItem.config.showAccept 45 | const input = angular.element( 46 | this.Element[0].querySelector('input[type=file]') 47 | ) 48 | if (input) { 49 | if (this.showAllowed) { 50 | input[0].setAttribute('accept', this.formItem.config.accept) 51 | } else { 52 | input[0].removeAttribute('accept') 53 | delete this.formItem.config.accept 54 | } 55 | } 56 | } 57 | 58 | _enableWatchers() { 59 | this.Scope.$watch('UploadView.formItem.config.multipleUpload', (newVal) => { 60 | if (newVal !== undefined) { 61 | this._updateMultiple() 62 | } 63 | }) 64 | 65 | this.Scope.$watch('UploadView.formItem.config.showAccept', (newVal) => { 66 | if (newVal !== undefined) { 67 | this._updateAccept() 68 | } 69 | }) 70 | 71 | this.Scope.$watch('UploadView.formItem.config.accept', (newVal) => { 72 | if (newVal !== undefined) { 73 | this._updateAccept() 74 | } 75 | }) 76 | } 77 | 78 | removeItem(index) { 79 | this.formItem.options.splice(index, 1) 80 | } 81 | } 82 | export { UploadViewCtrl } 83 | -------------------------------------------------------------------------------- /src/lib/directives/upload-item/upload-view.directive.js: -------------------------------------------------------------------------------- 1 | import { UploadViewCtrl } from './upload-view.controller' 2 | import UploadViewTemplate from './upload-view.tpl.html' 3 | 4 | const MB = 1024 * 1024 5 | 6 | class UploadView { 7 | /** 8 | * @ngInject 9 | * @param {ng.ITimeoutService} $timeout 10 | */ 11 | constructor($timeout) { 12 | this.$timeout = $timeout 13 | this.template = UploadViewTemplate 14 | this.restrict = 'E' 15 | this.scope = { 16 | formItem: '=', 17 | isPreview: '&', 18 | form: '=', 19 | } 20 | this.controller = UploadViewCtrl 21 | this.controllerAs = 'UploadView' 22 | this.bindToController = true 23 | } 24 | 25 | /** 26 | * @see https://docs.angularjs.org/api/ng/service/$compile#-link- 27 | * @param {ng.IScope} scope - scope 28 | * @param {JQLite} element - element 29 | * @param {ng.IAttributes} attrs - attributes 30 | * @param {UploadViewCtrl} ctrl - this instance controller 31 | * @param {ng.ITranscludeFunction} transcludeFn - transclude function ($transclude) 32 | */ 33 | link(scope, element, attrs, ctrl) { 34 | //this timeout is placed here in order to make sure that the creator directive of this view is finished its work 35 | this.$timeout(function () { 36 | ctrl.init() 37 | }, 50) 38 | 39 | const button = angular.element(element[0].querySelector('.upload-button')) 40 | const input = angular.element(element[0].querySelector('input[type=file]')) 41 | const label = angular.element(element[0].querySelector('label')) 42 | 43 | if (label.length) { 44 | label.css('display', 'none') 45 | } 46 | 47 | button.on('click', () => { 48 | label.css('display', 'none') 49 | typeof input.trigger === 'function' 50 | ? input.trigger('click') 51 | : input[0].click() 52 | }) 53 | 54 | input.on('change', (e) => { 55 | scope.$apply(function () { 56 | /** 57 | * @type {File[]} 58 | */ 59 | const files = Array.from(e.target.files) 60 | // Max allowed size in MB 61 | const maxSizeMB = ctrl.formItem.config.size * MB 62 | const exceedsSize = files.some((file) => file.size >= maxSizeMB) 63 | if (exceedsSize) { 64 | label.css('display', 'block') 65 | label.text(ctrl.formItem.config.sizeErrMessage) 66 | ctrl.formItem.options = [] 67 | } else { 68 | ctrl.formItem.options = files.map((file) => { 69 | const { name, size, type } = file 70 | return { name, size, type, file } 71 | }) 72 | } 73 | }) 74 | }) 75 | } 76 | } 77 | 78 | export { UploadView } 79 | -------------------------------------------------------------------------------- /src/lib/directives/upload-item/upload-view.tpl.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | attach_file{{UploadView.formItem.config.uploadFileButtonLabel}} 21 |
22 | 23 |
24 | 25 |
30 | 31 | close 32 | 33 | {{option.name}} 34 |
35 |
36 | -------------------------------------------------------------------------------- /src/lib/index.module.js: -------------------------------------------------------------------------------- 1 | import './index.scss' 2 | import { UploadItem } from './directives/upload-item/upload-item.directive' 3 | import { UploadView } from './directives/upload-item/upload-view.directive' 4 | import { AgreementItem } from './directives/agreement-item/agreement-item.directive' 5 | import { AgreementView } from './directives/agreement-item/agreement-view.directive' 6 | import { MainController } from './main/main.controller' 7 | import { Utils } from './utils/utils.service' 8 | import { CheckboxesItem } from './directives/checkboxes-item/checkboxes-item.directive' 9 | import { CheckboxesView } from './directives/checkboxes-item/checkboxes-view.directive' 10 | import { FormItem } from './directives/form-item/form-item.directive' 11 | import { FormItemsContainer } from './directives/form-items-container/form-items-container.directive' 12 | import { FormView } from './directives/form-view/form-view.directive' 13 | import { InputItem } from './directives/input-item/input-item.directive' 14 | import { InputView } from './directives/input-item/input-view.directive' 15 | import { LabelItem } from './directives/label-item/label-item.directive' 16 | import { LabelView } from './directives/label-item/label-view.directive' 17 | import { MatrixItem } from './directives/matrix-item/matrix-item.directive' 18 | import { MatrixView } from './directives/matrix-item/matrix-view.directive' 19 | import { RadioButtonItem } from './directives/radio-button-item/radio-button-item.directive' 20 | import { RadioButtonView } from './directives/radio-button-item/radio-button-view.directive' 21 | import { SelectView } from './directives/select-item/select-view.directive' 22 | import { SelectItem } from './directives/select-item/select-item.directive' 23 | import { TextareaItem } from './directives/textarea-item/textarea-item.directive' 24 | import { TextareaView } from './directives/textarea-item/textarea-view.directive' 25 | 26 | export default angular 27 | .module('angularMaterialFormBuilder', [ 28 | 'ngMaterial', 29 | 'angular-sortable-view', 30 | 'ngMessages', 31 | ]) 32 | .service('Utils', Utils) 33 | .controller('MainController', MainController) 34 | .directive('uploadItem', UploadItem) 35 | .directive('uploadView', UploadView) 36 | .directive('agreementItem', AgreementItem) 37 | .directive('agreementView', AgreementView) 38 | .directive('checkboxesItem', CheckboxesItem) 39 | .directive('checkboxesView', CheckboxesView) 40 | .directive('formItem', FormItem) 41 | .directive('formItemsContainer', FormItemsContainer) 42 | .directive('formView', FormView) 43 | .directive('inputItem', InputItem) 44 | .directive('inputView', InputView) 45 | .directive('labelItem', LabelItem) 46 | .directive('labelView', LabelView) 47 | .directive('matrixItem', MatrixItem) 48 | .directive('matrixView', MatrixView) 49 | .directive('radioButtonItem', RadioButtonItem) 50 | .directive('radioButtonView', RadioButtonView) 51 | .directive('selectItem', SelectItem) 52 | .directive('selectView', SelectView) 53 | .directive('textareaItem', TextareaItem) 54 | .directive('textareaView', TextareaView) 55 | -------------------------------------------------------------------------------- /src/lib/index.scss: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/icon?family=Material+Icons); 2 | 3 | $button-dim: 36px; 4 | 5 | .main-view { 6 | .content { 7 | .builder, 8 | .view, 9 | .json { 10 | border-right: 1px solid #cccccc; 11 | height: 800px; 12 | overflow: auto; 13 | } 14 | } 15 | } 16 | 17 | .builder-icon { 18 | min-height: $button-dim; 19 | min-width: $button-dim; 20 | height: $button-dim; 21 | width: $button-dim; 22 | line-height: 0; 23 | } 24 | 25 | form-item { 26 | .option-item { 27 | min-height: 75px; 28 | 29 | .md-button { 30 | @extend .builder-icon; 31 | line-height: 0; 32 | margin-top: 20px; 33 | 34 | &.handle:active, 35 | &.handle:hover { 36 | cursor: move; 37 | } 38 | } 39 | } 40 | 41 | .form-item-container { 42 | position: relative; 43 | padding-top: 30px; 44 | 45 | .form-item-actions { 46 | position: absolute; 47 | right: 20px; 48 | top: 0; 49 | } 50 | 51 | .md-button { 52 | @extend .builder-icon; 53 | } 54 | } 55 | } 56 | 57 | form-view { 58 | .formItem-title { 59 | font-size: 18px; 60 | } 61 | 62 | // .formItem-content { 63 | // font-size: 18px; 64 | // } 65 | 66 | .formItem-help-text { 67 | font-size: 14px; 68 | color: #6c6c6c; 69 | } 70 | 71 | .matrix-container { 72 | overflow: auto; 73 | 74 | .matrix { 75 | .matrix-row { 76 | border-bottom: 1px solid #4caf50; 77 | } 78 | 79 | .matrix-cell { 80 | text-align: center; 81 | overflow: hidden; 82 | } 83 | 84 | md-radio-button { 85 | .md-label { 86 | margin-left: 0; 87 | margin-right: 0; 88 | } 89 | } 90 | 91 | md-radio-button, 92 | .md-switch-thumb { 93 | margin: 15px 0; 94 | } 95 | } 96 | } 97 | } 98 | 99 | i.material-icons { 100 | position: absolute; 101 | top: 50%; 102 | left: 50%; 103 | transform: translate(-50%, -50%); 104 | 105 | &.medium { 106 | font-size: 36px; 107 | } 108 | } 109 | 110 | .md-button.upload-button { 111 | padding: 0 16px; 112 | height: 36px; 113 | md-icon { 114 | font-size: 16px; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/lib/main/main.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {{type:string}} Item 3 | * @typedef {{items: Item[]}} FormConfig 4 | */ 5 | 6 | class MainController { 7 | /** 8 | * @ngInject 9 | */ 10 | constructor() { 11 | /** 12 | * @type {FormConfig} 13 | */ 14 | this.form = { 15 | items: [], 16 | } 17 | } 18 | 19 | /** 20 | * Add new Item 21 | * @param {string} type 22 | */ 23 | addItem(type) { 24 | this.form.items.push({ 25 | type, 26 | }) 27 | } 28 | 29 | /** 30 | * Remove item at index 31 | * @param {Item} item 32 | * @param {number} index 33 | */ 34 | delete(item, index) { 35 | this.form.items.splice(index, 1) 36 | } 37 | 38 | /** 39 | * insert before (bounded) 40 | * Pops out latest element (wanted?) 41 | * @param {Item} item 42 | * @param {number} index 43 | */ 44 | up(item, index) { 45 | if (index !== 0) { 46 | const prevItem = this.form.items[index - 1] 47 | this.form.items[index] = prevItem 48 | this.form.items[index - 1] = item 49 | } 50 | } 51 | 52 | /** 53 | * insert after (bounded) 54 | * Pops out latest element (wanted?) 55 | * @param {Item} item 56 | * @param {number} index 57 | */ 58 | down(item, index) { 59 | if (index !== this.form.items.length - 1) { 60 | const nextItem = this.form.items[index + 1] 61 | this.form.items[index] = nextItem 62 | this.form.items[index + 1] = item 63 | } 64 | } 65 | } 66 | 67 | export { MainController } 68 | -------------------------------------------------------------------------------- /src/lib/main/main.controller.test.js: -------------------------------------------------------------------------------- 1 | import { createTestApp } from 'angularjs-jest' 2 | import angularMaterialFormBuilder from '../index.module' 3 | import { MainController } from './main.controller' 4 | 5 | describe('Main Controller test', () => { 6 | const createTestAppWithMainController = (MainController) => 7 | createTestApp({ 8 | modules: [angularMaterialFormBuilder], 9 | mocks: { MainController }, 10 | access: ['MainController'], 11 | }) 12 | 13 | it('MainController constructor', () => { 14 | const mc = new MainController() 15 | expect(mc).toBeDefined() 16 | expect(Array.isArray(mc.form.items)).toBe(true) 17 | }) 18 | 19 | it('MainController methods', () => { 20 | /** 21 | * @type {{MainController: import('./main.controller').MainController}} 22 | */ 23 | const testApp = createTestAppWithMainController(() => new MainController()) 24 | expect(testApp.MainController).toBeDefined() 25 | const mc = testApp.MainController 26 | 27 | // Add 28 | mc.addItem('checkbox') 29 | mc.addItem('select') 30 | mc.addItem('input') 31 | mc.addItem('label') 32 | expect(mc.form.items[0].type).toBe('checkbox') 33 | expect(mc.form.items[1].type).toBe('select') 34 | expect(mc.form.items[2].type).toBe('input') 35 | expect(mc.form.items[3].type).toBe('label') 36 | 37 | // Remove 38 | mc.delete(null, 1) 39 | expect(mc.form.items.length).toBe(3) 40 | expect(mc.form.items[0].type).toBe('checkbox') 41 | expect(mc.form.items[1].type).toBe('input') 42 | expect(mc.form.items[2].type).toBe('label') 43 | 44 | // Insert before - pops last out 45 | mc.up({ type: 'select' }, 2) 46 | expect(mc.form.items[0].type).toBe('checkbox') 47 | expect(mc.form.items[1].type).toBe('select') 48 | expect(mc.form.items[2].type).toBe('input') 49 | // Index 0 will do nothing 50 | mc.up({ type: 'radio' }, 0) 51 | expect(mc.form.items[0].type).toBe('checkbox') 52 | expect(mc.form.items[1].type).toBe('select') 53 | expect(mc.form.items[2].type).toBe('input') 54 | 55 | // Insert after - pops first out 56 | mc.down({ type: 'radio' }, 0) 57 | expect(mc.form.items[0].type).toBe('select') 58 | expect(mc.form.items[1].type).toBe('radio') 59 | expect(mc.form.items[2].type).toBe('input') 60 | // Last item will do nothing 61 | mc.down({ type: 'select' }, 2) 62 | expect(mc.form.items[0].type).toBe('select') 63 | expect(mc.form.items[1].type).toBe('radio') 64 | expect(mc.form.items[2].type).toBe('input') 65 | }) 66 | }) 67 | -------------------------------------------------------------------------------- /src/lib/utils/utils.service.js: -------------------------------------------------------------------------------- 1 | class Utils { 2 | /** 3 | * Recursively extend object properties 4 | * @param {Object} dest 5 | * @param {Object} src 6 | * @returns {Object} 7 | */ 8 | extend(dest, src) { 9 | return Object.keys(src).reduce( 10 | (result, key) => { 11 | if (typeof result[key] === 'undefined') { 12 | result[key] = src[key] 13 | } else if (typeof src[key] === 'object') { 14 | result[key] = this.extend(result[key], src[key]) 15 | } 16 | return result 17 | }, 18 | typeof dest === 'undefined' ? {} : dest 19 | ) 20 | } 21 | } 22 | 23 | export { Utils } 24 | -------------------------------------------------------------------------------- /src/lib/utils/utils.service.test.js: -------------------------------------------------------------------------------- 1 | import { createTestApp } from 'angularjs-jest' 2 | import angularMaterialFormBuilder from '../index.module' 3 | import { Utils } from './utils.service' 4 | 5 | describe('Utils Service test', () => { 6 | const createTestAppWithUtils = (Utils) => 7 | createTestApp({ 8 | modules: [angularMaterialFormBuilder], 9 | mocks: { Utils }, 10 | access: ['Utils'], 11 | }) 12 | 13 | it('Utils constructor', () => { 14 | const u = new Utils() 15 | expect(u).toBeDefined() 16 | }) 17 | 18 | it('Utils extend', () => { 19 | /** 20 | * @type {{Utils: import('./utils.service').Utils}} 21 | */ 22 | const testApp = createTestAppWithUtils(() => new Utils()) 23 | expect(testApp.Utils).toBeDefined() 24 | 25 | expect(testApp.Utils.extend(undefined, { a: 1 })).toStrictEqual({ a: 1 }) 26 | 27 | expect(testApp.Utils.extend({ a: { b: 2 } }, { a: 1 })).toStrictEqual({ 28 | a: { b: 2 }, 29 | }) 30 | 31 | expect( 32 | testApp.Utils.extend( 33 | { config: { value: 1 } }, 34 | { 35 | config: { 36 | maxSelections: null, 37 | }, 38 | options: [ 39 | { 40 | value: '', 41 | selected: false, 42 | }, 43 | ], 44 | } 45 | ) 46 | ).toStrictEqual({ 47 | config: { 48 | value: 1, 49 | maxSelections: null, 50 | }, 51 | options: [ 52 | { 53 | value: '', 54 | selected: false, 55 | }, 56 | ], 57 | }) 58 | }) 59 | }) 60 | -------------------------------------------------------------------------------- /src/setup-jest.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | global.mocha = true 3 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const { CleanWebpackPlugin } = require('clean-webpack-plugin') 2 | const MiniCssExtractPlugin = require('mini-css-extract-plugin') 3 | const CssMinimizerPlugin = require('css-minimizer-webpack-plugin') 4 | const TerserPlugin = require('terser-webpack-plugin') 5 | const ESLintPlugin = require('eslint-webpack-plugin') 6 | const path = require('path') 7 | 8 | // Dev Server variables 9 | const DEV_SERVER_HOST = process.env.DEV_HOST || '127.0.0.1' 10 | const DEV_SERVER_PORT = process.env.DEV_PORT || 8080 11 | 12 | /** 13 | * Webpack config factory 14 | * @returns {import('webpack').Configuration} 15 | */ 16 | async function webpackConfig() { 17 | const main = path.resolve(__dirname, 'src/lib/index.module.js') 18 | return { 19 | entry: { 20 | 'angular-material-form-builder': main, 21 | 'angular-material-form-builder.min': main, 22 | }, 23 | output: { 24 | path: path.resolve(__dirname, 'dist'), 25 | filename: '[name].js', 26 | library: 'angular-material-form-builder', 27 | libraryTarget: 'umd', 28 | }, 29 | devServer: { 30 | open: true, 31 | host: DEV_SERVER_HOST, 32 | port: DEV_SERVER_PORT, 33 | historyApiFallback: true, 34 | static: { 35 | directory: path.resolve(__dirname), 36 | publicPath: '/', 37 | }, 38 | devMiddleware: { 39 | writeToDisk: true, 40 | }, 41 | client: { 42 | logging: 'info', 43 | // Can be used only for `errors`/`warnings` 44 | // 45 | // overlay: { 46 | // errors: true, 47 | // warnings: true, 48 | // } 49 | overlay: true, 50 | progress: true, 51 | }, 52 | }, 53 | devtool: 'source-map', 54 | externals: { 55 | $: 'jquery', 56 | jQuery: 'jquery', 57 | 'window.jQuery': 'jquery', 58 | angular: 'angular', 59 | 'angular-material': 'angular-material', 60 | 'angular-messages': 'angular-messages', 61 | }, 62 | plugins: [ 63 | new CleanWebpackPlugin(), 64 | new MiniCssExtractPlugin({ 65 | filename: '[name].css', 66 | }), 67 | new ESLintPlugin(), 68 | ], 69 | optimization: { 70 | minimize: true, 71 | minimizer: [ 72 | new TerserPlugin({ 73 | include: /\.min\.js$/, 74 | terserOptions: { 75 | format: { 76 | comments: false, 77 | }, 78 | }, 79 | extractComments: false, 80 | }), 81 | new CssMinimizerPlugin({ include: /\.min\.css$/ }), 82 | ], 83 | }, 84 | module: { 85 | rules: [ 86 | { 87 | test: /.s?css$/, 88 | use: [ 89 | MiniCssExtractPlugin.loader, 90 | { 91 | loader: 'css-loader', 92 | options: { 93 | url: false, // turn off URL resolution 94 | }, 95 | }, 96 | { 97 | loader: 'sass-loader', 98 | options: { 99 | // // Prefer `node-sass` 100 | // implementation: require('node-sass'), 101 | // Prefer `dart-sass` 102 | implementation: require('sass'), 103 | sassOptions: { 104 | fiber: false, 105 | }, 106 | }, 107 | }, 108 | ], 109 | }, 110 | { 111 | test: /\.(png|jpg|gif)$/i, 112 | use: [ 113 | { 114 | loader: 'url-loader', 115 | options: { 116 | limit: 8192, 117 | }, 118 | }, 119 | ], 120 | }, 121 | { 122 | test: /\.js$/, 123 | exclude: /node_modules/, 124 | use: ['babel-loader'], 125 | }, 126 | { 127 | test: /\.tpl.html$/i, 128 | loader: 'raw-loader', 129 | }, 130 | ], 131 | }, 132 | } 133 | } 134 | 135 | module.exports = webpackConfig 136 | --------------------------------------------------------------------------------