├── .editorconfig ├── .github └── workflows │ ├── ci.yml │ └── codeql-analysis.yml ├── .gitignore ├── .nvmrc ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── UPGRADING.md ├── addon ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .template-lintrc.js ├── addon-main.js ├── babel.config.json ├── blueprints │ └── ember-form-builder │ │ ├── files │ │ └── app │ │ │ └── services │ │ │ └── form-builder-registry.js │ │ └── index.js ├── config │ ├── ember-cli-update.json │ └── environment.js ├── package.json ├── rollup.config.mjs └── src │ ├── .gitkeep │ ├── components │ ├── form-builder.hbs │ ├── form-builder.js │ ├── form-builder │ │ ├── fields.hbs │ │ ├── fields.js │ │ ├── input.hbs │ │ ├── input.js │ │ ├── label.hbs │ │ ├── label.js │ │ ├── submit.hbs │ │ └── submit.js │ ├── input-wrappers │ │ ├── default.hbs │ │ └── inline.hbs │ └── inputs │ │ ├── boolean-input.hbs │ │ ├── boolean-input.js │ │ ├── checkbox-option.hbs │ │ ├── checkbox-option.js │ │ ├── checkboxes-input.hbs │ │ ├── checkboxes-input.js │ │ ├── collection-input.hbs │ │ ├── collection-input.js │ │ ├── date-input.js │ │ ├── email-input.js │ │ ├── number-input.js │ │ ├── password-input.js │ │ ├── select-option.hbs │ │ ├── select-option.js │ │ ├── string-input.hbs │ │ ├── string-input.js │ │ ├── tel-input.js │ │ ├── text-input.hbs │ │ ├── text-input.js │ │ └── url-input.js │ ├── configuration.js │ ├── data-adapters │ ├── base.js │ ├── ember-data.js │ └── ember-orbit.js │ ├── helpers │ ├── register-form-builder-input.js │ └── register-form-builder-wrapper.js │ ├── models │ └── form-builder.js │ ├── services │ ├── form-builder-registry.js │ └── form-builder-translations.js │ ├── test-support │ ├── accessors │ │ ├── boolean.js │ │ ├── checkboxes.js │ │ ├── collection.js │ │ ├── date.js │ │ ├── number.js │ │ ├── registry.js │ │ ├── string.js │ │ └── text.js │ ├── fill-form.js │ ├── index.js │ ├── lib │ │ ├── expand.js │ │ ├── find-input.js │ │ └── flatten.js │ ├── pick.js │ ├── read-errors.js │ └── read-form.js │ ├── utils │ ├── compatible-proxy.js │ ├── guess-type.js │ ├── humanize.js │ └── optional-action.js │ └── validation-adapters │ ├── ember-cp-validations.js │ └── ember-validations.js ├── package.json ├── test-app ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc.js ├── .template-lintrc.js ├── .watchmanconfig ├── app │ ├── app.js │ ├── components │ │ ├── .gitkeep │ │ ├── input-wrappers │ │ │ └── my-wrapper.hbs │ │ ├── inputs │ │ │ └── log-input.hbs │ │ ├── my-input.hbs │ │ └── my-label.hbs │ ├── controllers │ │ ├── .gitkeep │ │ └── index.js │ ├── helpers │ │ └── .gitkeep │ ├── index.html │ ├── models │ │ └── .gitkeep │ ├── router.js │ ├── routes │ │ ├── .gitkeep │ │ └── index.js │ ├── services │ │ └── form-builder-registry.js │ ├── styles │ │ └── app.css │ ├── templates │ │ ├── application.hbs │ │ └── index.hbs │ ├── validation-adapters │ │ └── dummy.js │ └── views │ │ └── .gitkeep ├── config │ ├── ember-cli-update.json │ ├── ember-try.js │ ├── environment.js │ ├── optional-features.json │ └── targets.js ├── ember-cli-build.js ├── package.json ├── public │ └── robots.txt ├── testem.js └── tests │ ├── acceptance │ └── index-test.js │ ├── helpers │ └── index.js │ ├── index.html │ ├── integration │ ├── components │ │ ├── form-builder-test.js │ │ ├── form-builder │ │ │ ├── fields-test.js │ │ │ ├── input-test.js │ │ │ ├── label-test.js │ │ │ └── submit-test.js │ │ ├── input-wrappers │ │ │ ├── default-test.js │ │ │ └── inline-test.js │ │ └── inputs │ │ │ ├── boolean-input-test.js │ │ │ ├── checkboxes-input-test.js │ │ │ ├── collection-input-test.js │ │ │ ├── date-input-test.js │ │ │ ├── email-input-test.js │ │ │ ├── number-input-test.js │ │ │ ├── password-input-test.js │ │ │ ├── select-option-test.js │ │ │ ├── string-input-test.js │ │ │ ├── tel-input-test.js │ │ │ ├── text-input-test.js │ │ │ └── url-input-test.js │ └── test-support │ │ ├── default-accessors-test.js │ │ ├── fill-form-test.js │ │ ├── pick-test.js │ │ ├── read-errors-test.js │ │ └── read-form-test.js │ ├── test-helper.js │ └── unit │ ├── .gitkeep │ ├── data-adapters │ ├── base-test.js │ ├── ember-data-test.js │ └── ember-orbit-test.js │ ├── models │ └── form-builder-test.js │ ├── services │ ├── form-builder-registry-test.js │ └── form-builder-translations-test.js │ ├── utilites │ └── guess-type-test.js │ └── validation-adapters │ ├── ember-cp-validations-test.js │ └── ember-validations-test.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: {} 9 | 10 | concurrency: 11 | group: ci-${{ github.head_ref || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | test: 16 | name: "Tests" 17 | runs-on: ubuntu-latest 18 | timeout-minutes: 10 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Install Node 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: 18.x 26 | cache: yarn 27 | - name: Install Dependencies 28 | run: yarn install --frozen-lockfile 29 | - name: Lint Addon 30 | run: yarn lint 31 | working-directory: addon 32 | - name: Lint 33 | run: yarn lint 34 | working-directory: test-app 35 | - name: Build addon 36 | run: yarn build 37 | working-directory: addon 38 | - name: Run Tests 39 | run: yarn test:ember 40 | working-directory: test-app 41 | 42 | floating: 43 | name: "Floating Dependencies" 44 | runs-on: ubuntu-latest 45 | timeout-minutes: 10 46 | 47 | steps: 48 | - uses: actions/checkout@v3 49 | - uses: actions/setup-node@v3 50 | with: 51 | node-version: 18.x 52 | cache: yarn 53 | - name: Install Dependencies 54 | run: yarn install --no-lockfile 55 | - name: Build addon 56 | run: yarn build 57 | working-directory: addon 58 | - name: Run Tests 59 | run: yarn test:ember 60 | working-directory: test-app 61 | 62 | try-scenarios: 63 | name: ${{ matrix.try-scenario }} 64 | runs-on: ubuntu-latest 65 | needs: "test" 66 | timeout-minutes: 10 67 | 68 | strategy: 69 | fail-fast: false 70 | matrix: 71 | try-scenario: 72 | - ember-lts-3.28 73 | - ember-lts-4.4 74 | - ember-lts-4.12 75 | - ember-lts-5.4 76 | - ember-release 77 | - ember-beta 78 | - ember-canary 79 | - embroider-safe 80 | - embroider-optimized 81 | 82 | steps: 83 | - uses: actions/checkout@v3 84 | - name: Install Node 85 | uses: actions/setup-node@v3 86 | with: 87 | node-version: 18.x 88 | cache: yarn 89 | - name: Install Dependencies 90 | run: yarn install --frozen-lockfile 91 | - name: Build addon 92 | run: yarn build 93 | working-directory: addon 94 | - name: Run Tests 95 | run: ./node_modules/.bin/ember try:one ${{ matrix.try-scenario }} 96 | working-directory: test-app 97 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "master" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '32 0 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # you definitely want this: 2 | node_modules 3 | 4 | yarn-error.log 5 | .DS_Store 6 | .eslintcache 7 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.16 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | - `git clone ` 6 | - `cd my-addon` 7 | - `npm install` 8 | 9 | ## Linting 10 | 11 | - `npm run lint` 12 | - `npm run lint:fix` 13 | 14 | ## Running tests 15 | 16 | - `ember test` – Runs the test suite on the current Ember version 17 | - `ember test --server` – Runs the test suite in "watch mode" 18 | - `ember try:each` – Runs the test suite against multiple Ember versions 19 | 20 | ## Running the test application 21 | 22 | - `ember serve` 23 | - Visit the test application at [http://localhost:4200](http://localhost:4200). 24 | 25 | For more information on using ember-cli, visit [https://cli.emberjs.com/release/](https://cli.emberjs.com/release/). 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /addon/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /addon/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | ecmaVersion: 2018, 8 | sourceType: 'module', 9 | ecmaFeatures: { 10 | legacyDecorators: true, 11 | }, 12 | }, 13 | plugins: ['ember'], 14 | extends: [ 15 | 'eslint:recommended', 16 | 'plugin:ember/recommended', 17 | 'plugin:prettier/recommended', 18 | ], 19 | env: { 20 | browser: true, 21 | }, 22 | rules: { 23 | 'ember/no-get': 'off', 24 | 'ember/classic-decorator-hooks': 'error', 25 | 'ember/classic-decorator-no-classic-methods': 'error', 26 | }, 27 | overrides: [ 28 | // node files 29 | { 30 | files: [ 31 | './.eslintrc.js', 32 | './.prettierrc.js', 33 | './.template-lintrc.js', 34 | './ember-cli-build.js', 35 | './addon-main.js', 36 | './testem.js', 37 | './blueprints/*/index.js', 38 | './config/**/*.js', 39 | ], 40 | parserOptions: { 41 | sourceType: 'script', 42 | }, 43 | env: { 44 | browser: false, 45 | node: true, 46 | }, 47 | plugins: ['node'], 48 | extends: ['plugin:node/recommended'], 49 | }, 50 | { 51 | // test files 52 | files: ['tests/**/*-test.{js,ts}'], 53 | extends: ['plugin:qunit/recommended'], 54 | }, 55 | ], 56 | }; 57 | -------------------------------------------------------------------------------- /addon/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /.eslintcache 16 | /connect.lock 17 | /coverage/ 18 | /libpeerconnection.log 19 | /npm-debug.log* 20 | /testem.log 21 | /yarn-error.log 22 | 23 | # ember-try 24 | /.node_modules.ember-try/ 25 | /bower.json.ember-try 26 | /npm-shrinkwrap.json.ember-try 27 | /package.json.ember-try 28 | /package-lock.json.ember-try 29 | /yarn.lock.ember-try 30 | 31 | # broccoli-debug 32 | /DEBUG/ 33 | -------------------------------------------------------------------------------- /addon/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | .lint-todo/ 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /addon/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | }; 6 | -------------------------------------------------------------------------------- /addon/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /addon/addon-main.js: -------------------------------------------------------------------------------- 1 | const { addonV1Shim } = require('@embroider/addon-shim'); 2 | module.exports = addonV1Shim(__dirname); 3 | -------------------------------------------------------------------------------- /addon/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@embroider/addon-dev/template-colocation-plugin", 4 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 5 | "@babel/plugin-proposal-class-properties" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /addon/blueprints/ember-form-builder/files/app/services/form-builder-registry.js: -------------------------------------------------------------------------------- 1 | import OriginalRegistry from 'ember-form-builder/services/form-builder-registry'; 2 | import { importSync } from '@embroider/macros'; 3 | import { ensureSafeComponent } from '@embroider/util'; 4 | 5 | // This service eagerly loads all FormBuilder inputs and input wrappers 6 | // If you need more control over what gets imported, delete this file 7 | // and use register-form-builder-input helper 8 | // or FormBuilderRegistryService.registerInput() instead 9 | 10 | export default class FormBuilderRegistryService extends OriginalRegistry { 11 | resolveInput(type) { 12 | return ensureSafeComponent(importSync(`../components/inputs/${type}-input.js`).default, this); 13 | } 14 | 15 | resolveWrapper(type) { 16 | return ensureSafeComponent(importSync(`../components/input-wrappers/${type}.js`).default, this); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /addon/blueprints/ember-form-builder/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | description: '', 5 | 6 | normalizeEntityName() {}, 7 | 8 | // locals(options) { 9 | // // Return custom template variables here. 10 | // return { 11 | // foo: options.entity.options.foo 12 | // }; 13 | // } 14 | 15 | // afterInstall(options) { 16 | // // Perform extra work here. 17 | // } 18 | }; 19 | -------------------------------------------------------------------------------- /addon/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "4.8.0", 7 | "blueprints": [ 8 | { 9 | "name": "addon", 10 | "outputRepo": "https://github.com/ember-cli/ember-addon-output", 11 | "codemodsSource": "ember-addon-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--no-welcome" 15 | ] 16 | } 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /addon/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (/* environment, appConfig */) { 4 | return {}; 5 | }; 6 | -------------------------------------------------------------------------------- /addon/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-form-builder", 3 | "version": "3.0.0-beta.1", 4 | "description": "Simple and easily extensible form builder for Ember apps", 5 | "engines": { 6 | "node": "14.* || 16.* || >= 18" 7 | }, 8 | "scripts": { 9 | "lint": "npm-run-all --print-name --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", 10 | "lint:fix": "npm-run-all --print-name --aggregate-output --continue-on-error --parallel \"lint:*:fix\"", 11 | "lint:hbs": "ember-template-lint .", 12 | "lint:hbs:fix": "ember-template-lint . --fix", 13 | "lint:js": "eslint . --cache", 14 | "lint:js:fix": "eslint . --fix", 15 | "build": "rollup --config", 16 | "prepublishOnly": "rollup --config", 17 | "start": "rollup --config --watch" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.19.6", 21 | "@babel/plugin-proposal-class-properties": "^7.18.6", 22 | "@babel/plugin-proposal-decorators": "^7.19.6", 23 | "@ember/optional-features": "^2.0.0", 24 | "@embroider/addon-dev": "^3.0.0", 25 | "@embroider/compat": "^2.0.2", 26 | "@glimmer/component": "^1.1.2", 27 | "@glimmer/tracking": "^1.1.2", 28 | "@rollup/plugin-babel": "^6.0.2", 29 | "babel-eslint": "^10.1.0", 30 | "ember-template-lint": "^4.16.1", 31 | "eslint": "^7.32.0", 32 | "eslint-config-prettier": "^8.5.0", 33 | "eslint-plugin-ember": "^11.1.0", 34 | "eslint-plugin-node": "^11.1.0", 35 | "npm-run-all": "^4.1.5", 36 | "prettier": "^2.7.1", 37 | "rollup": "^3.2.3" 38 | }, 39 | "keywords": [ 40 | "ember-addon" 41 | ], 42 | "repository": { 43 | "type": "git", 44 | "url": "https://github.com/nibynic/ember-form-builder" 45 | }, 46 | "license": "MIT", 47 | "author": "", 48 | "directories": { 49 | "doc": "doc", 50 | "test": "tests" 51 | }, 52 | "ember": { 53 | "edition": "octane" 54 | }, 55 | "ember-addon": { 56 | "main": "addon-main.js", 57 | "type": "addon", 58 | "version": 2, 59 | "app-js": { 60 | "./components/form-builder.js": "./dist/_app_/components/form-builder.js", 61 | "./components/form-builder/fields.js": "./dist/_app_/components/form-builder/fields.js", 62 | "./components/form-builder/input.js": "./dist/_app_/components/form-builder/input.js", 63 | "./components/form-builder/label.js": "./dist/_app_/components/form-builder/label.js", 64 | "./components/form-builder/submit.js": "./dist/_app_/components/form-builder/submit.js", 65 | "./components/input-wrappers/default.js": "./dist/_app_/components/input-wrappers/default.js", 66 | "./components/input-wrappers/inline.js": "./dist/_app_/components/input-wrappers/inline.js", 67 | "./components/inputs/boolean-input.js": "./dist/_app_/components/inputs/boolean-input.js", 68 | "./components/inputs/checkbox-option.js": "./dist/_app_/components/inputs/checkbox-option.js", 69 | "./components/inputs/checkboxes-input.js": "./dist/_app_/components/inputs/checkboxes-input.js", 70 | "./components/inputs/collection-input.js": "./dist/_app_/components/inputs/collection-input.js", 71 | "./components/inputs/date-input.js": "./dist/_app_/components/inputs/date-input.js", 72 | "./components/inputs/email-input.js": "./dist/_app_/components/inputs/email-input.js", 73 | "./components/inputs/number-input.js": "./dist/_app_/components/inputs/number-input.js", 74 | "./components/inputs/password-input.js": "./dist/_app_/components/inputs/password-input.js", 75 | "./components/inputs/select-option.js": "./dist/_app_/components/inputs/select-option.js", 76 | "./components/inputs/string-input.js": "./dist/_app_/components/inputs/string-input.js", 77 | "./components/inputs/tel-input.js": "./dist/_app_/components/inputs/tel-input.js", 78 | "./components/inputs/text-input.js": "./dist/_app_/components/inputs/text-input.js", 79 | "./components/inputs/url-input.js": "./dist/_app_/components/inputs/url-input.js", 80 | "./data-adapters/base.js": "./dist/_app_/data-adapters/base.js", 81 | "./data-adapters/ember-data.js": "./dist/_app_/data-adapters/ember-data.js", 82 | "./data-adapters/ember-orbit.js": "./dist/_app_/data-adapters/ember-orbit.js", 83 | "./helpers/register-form-builder-input.js": "./dist/_app_/helpers/register-form-builder-input.js", 84 | "./helpers/register-form-builder-wrapper.js": "./dist/_app_/helpers/register-form-builder-wrapper.js", 85 | "./models/form-builder.js": "./dist/_app_/models/form-builder.js", 86 | "./services/form-builder-registry.js": "./dist/_app_/services/form-builder-registry.js", 87 | "./services/form-builder-translations.js": "./dist/_app_/services/form-builder-translations.js", 88 | "./validation-adapters/ember-cp-validations.js": "./dist/_app_/validation-adapters/ember-cp-validations.js", 89 | "./validation-adapters/ember-validations.js": "./dist/_app_/validation-adapters/ember-validations.js" 90 | } 91 | }, 92 | "dependencies": { 93 | "@ember/string": "^3.1.1", 94 | "@embroider/addon-shim": "^1.8.4", 95 | "@embroider/util": "^1.9.0", 96 | "ember-cache-primitive-polyfill": "^1.0.1", 97 | "ember-inflector": "^4.0.0" 98 | }, 99 | "peerDependencies": { 100 | "@ember/test-helpers": "^2.7.0", 101 | "ember-source": ">=3.24.0" 102 | }, 103 | "exports": { 104 | ".": "./dist/index.js", 105 | "./*": "./dist/*", 106 | "./test-support": "./dist/test-support/index.js", 107 | "./addon-main.js": "./addon-main.js" 108 | }, 109 | "files": [ 110 | "addon-main.js", 111 | "dist" 112 | ] 113 | } 114 | -------------------------------------------------------------------------------- /addon/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel'; 2 | import { Addon } from '@embroider/addon-dev/rollup'; 3 | 4 | const addon = new Addon({ 5 | srcDir: 'src', 6 | destDir: 'dist', 7 | }); 8 | 9 | export default { 10 | // This provides defaults that work well alongside `publicEntrypoints` below. 11 | // You can augment this if you need to. 12 | output: addon.output(), 13 | 14 | plugins: [ 15 | // These are the modules that users should be able to import from your 16 | // addon. Anything not listed here may get optimized away. 17 | addon.publicEntrypoints([ 18 | 'components/**/*.js', 19 | 'data-adapters/*.js', 20 | 'helpers/*.js', 21 | 'models/*.js', 22 | 'services/*.js', 23 | 'utils/*.js', 24 | 'validation-adapters/*.js', 25 | 'configuration.js', 26 | 'registry.js', 27 | 'test-support/index.js' 28 | ]), 29 | 30 | // These are the modules that should get reexported into the traditional 31 | // "app" tree. Things in here should also be in publicEntrypoints above, but 32 | // not everything in publicEntrypoints necessarily needs to go here. 33 | addon.appReexports([ 34 | 'components/*.js', 35 | 'components/**/*.js', 36 | 'data-adapters/*.js', 37 | 'helpers/*.js', 38 | 'models/*.js', 39 | 'services/*.js', 40 | 'validation-adapters/*.js' 41 | ]), 42 | 43 | // This babel config should *not* apply presets or compile away ES modules. 44 | // It exists only to provide development niceties for you, like automatic 45 | // template colocation. 46 | // 47 | // By default, this will load the actual babel config from the file 48 | // babel.config.json. 49 | babel({ 50 | babelHelpers: 'bundled', 51 | }), 52 | 53 | // Follow the V2 Addon rules about dependencies. Your code can import from 54 | // `dependencies` and `peerDependencies` as well as standard Ember-provided 55 | // package names. 56 | addon.dependencies(), 57 | 58 | // Ensure that standalone .hbs files are properly integrated as Javascript. 59 | addon.hbs(), 60 | 61 | // addons are allowed to contain imports of .css files, which we want rollup 62 | // to leave alone and keep in the published output. 63 | addon.keepAssets(['**/*.css']), 64 | 65 | // Remove leftover build artifacts when starting a new build. 66 | addon.clean(), 67 | ], 68 | }; 69 | -------------------------------------------------------------------------------- /addon/src/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nibynic/ember-form-builder/1da56d66cdfc08d4b8f43f5d2581a6a5017e7c00/addon/src/.gitkeep -------------------------------------------------------------------------------- /addon/src/components/form-builder.hbs: -------------------------------------------------------------------------------- 1 |
5 | {{yield (hash 6 | input=(component "form-builder/input" builder=this.formBuilder) 7 | fields=(component "form-builder/fields" on=this.formBuilder) 8 | submit=(component "form-builder/submit" builder=this.formBuilder) 9 | builder=this.formBuilder 10 | )}} 11 |
12 | -------------------------------------------------------------------------------- /addon/src/components/form-builder.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action } from '@ember/object'; 3 | import { getOwner } from '@ember/application'; 4 | import optional from '../utils/optional-action'; 5 | import { Settings } from './form-builder/fields'; 6 | 7 | export default class FormBuilderComponent extends Component { 8 | novalidate = false; 9 | 10 | @optional action() {} 11 | @optional submitFailed() {} 12 | 13 | @action 14 | submit(event) { 15 | event.preventDefault(); 16 | if (!('isLoading' in this.args)) { 17 | this.settings.isLoading = true; 18 | } 19 | if (!('status' in this.args)) { 20 | this.settings.status = undefined; 21 | } 22 | this.formBuilder 23 | .validate() 24 | .then(() => this.action()) 25 | .then( 26 | () => { 27 | if (!('status' in this.args)) { 28 | this.settings.status = 'success'; 29 | } 30 | }, 31 | (e) => { 32 | if (!('status' in this.args)) { 33 | this.settings.status = 'failure'; 34 | } 35 | this.submitFailed(e); 36 | throw e; 37 | } 38 | ) 39 | .finally(() => { 40 | if (!('isLoading' in this.args)) { 41 | this.settings.isLoading = false; 42 | } 43 | }) 44 | .catch(() => {}); 45 | } 46 | 47 | constructor() { 48 | super(...arguments); 49 | this.settings = new Settings(this.args); 50 | this.formBuilder = getOwner(this) 51 | .factoryFor('model:form-builder') 52 | .create({ settings: this.settings }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /addon/src/components/form-builder/fields.hbs: -------------------------------------------------------------------------------- 1 |
6 | {{yield (hash 7 | input=(component "form-builder/input" builder=this.formBuilder) 8 | fields=(component "form-builder/fields" on=this.formBuilder) 9 | submit=(component "form-builder/submit" builder=this.formBuilder) 10 | builder=this.formBuilder 11 | )}} 12 |
13 | -------------------------------------------------------------------------------- /addon/src/components/form-builder/fields.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action } from '@ember/object'; 3 | import { tracked } from '@glimmer/tracking'; 4 | import { dependentKeyCompat } from '@ember/object/compat'; 5 | import { getOwner } from '@ember/application'; 6 | 7 | export default class FieldsComponent extends Component { 8 | @action 9 | initializeBuilder() { 10 | this.args.on.addChild(this.formBuilder); 11 | } 12 | 13 | @action 14 | destroyBuilder() { 15 | this.args.on.removeChild(this.formBuilder); 16 | } 17 | 18 | constructor() { 19 | super(...arguments); 20 | this.settings = new Settings(this.args); 21 | this.formBuilder = getOwner(this) 22 | .factoryFor('model:form-builder') 23 | .create({ settings: this.settings }); 24 | } 25 | } 26 | 27 | const NOT_SET = Math.random(); 28 | 29 | export class Settings { 30 | @tracked source; 31 | 32 | constructor(source) { 33 | this.source = source; 34 | } 35 | 36 | @dependentKeyCompat 37 | get object() { 38 | return this.source.for; 39 | } 40 | 41 | @dependentKeyCompat 42 | get modelName() { 43 | return this.source.name; 44 | } 45 | 46 | @dependentKeyCompat 47 | get translationKey() { 48 | return this.source.translationKey; 49 | } 50 | 51 | @dependentKeyCompat 52 | get index() { 53 | return this.source.index; 54 | } 55 | 56 | @tracked _isLoading = NOT_SET; 57 | 58 | @dependentKeyCompat 59 | get isLoading() { 60 | return ( 61 | (this._isLoading !== NOT_SET && this._isLoading) || this.source.isLoading 62 | ); 63 | } 64 | set isLoading(v) { 65 | this._isLoading = v; 66 | } 67 | 68 | @tracked _status = NOT_SET; 69 | 70 | @dependentKeyCompat 71 | get status() { 72 | return (this._status !== NOT_SET && this._status) || this.source.status; 73 | } 74 | set status(v) { 75 | this._status = v; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /addon/src/components/form-builder/input.hbs: -------------------------------------------------------------------------------- 1 | {{#let this.wrapperComponent as |Wrapper|}} 2 | 11 | {{/let}} 12 | -------------------------------------------------------------------------------- /addon/src/components/form-builder/input.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { inject as service } from '@ember/service'; 4 | import { isPresent } from '@ember/utils'; 5 | import { defineProperty, action } from '@ember/object'; 6 | import humanize from '../../utils/humanize'; 7 | import guessType from '../../utils/guess-type'; 8 | import { A } from '@ember/array'; 9 | import { guidFor } from '@ember/object/internals'; 10 | import { dependentKeyCompat } from '@ember/object/compat'; 11 | import { once } from '@ember/runloop'; 12 | 13 | export default class InputComponent extends Component { 14 | @service('formBuilderTranslations') translationService; 15 | @service formBuilderRegistry; 16 | 17 | get value() { 18 | return this.args.builder.object[this.args.attr]; 19 | } 20 | set value(v) { 21 | this.args.builder.object[this.args.attr] = v; 22 | } 23 | 24 | get validations() { 25 | return (this.args.builder.validationAdapter.attributes || {})[ 26 | this.args.attr 27 | ]; 28 | } 29 | 30 | config = new ConfigProxy(this); 31 | texts = new TextProxy(this); 32 | 33 | get type() { 34 | return this.args.as || guessType(this.args.builder.model, this.args); 35 | } 36 | 37 | get inputComponent() { 38 | return this.formBuilderRegistry.resolveInput(this.type); 39 | } 40 | 41 | get wrapperComponent() { 42 | return this.formBuilderRegistry.resolveWrapper( 43 | this.args.wrapper || 'default' 44 | ); 45 | } 46 | 47 | get inputElementId() { 48 | return this.args.inputElementId || `${guidFor(this)}Input`; 49 | } 50 | 51 | get name() { 52 | var prefix = this.args.builder.name; 53 | var name = this.args.attr; 54 | if (isPresent(prefix)) { 55 | name = prefix + '[' + name + ']'; 56 | } 57 | return name; 58 | } 59 | 60 | get disabled() { 61 | return !!this.args.builder.isLoading || !!this.args.disabled; 62 | } 63 | 64 | @tracked 65 | hasFocusedOut = false; 66 | 67 | @action 68 | handleFocusOut() { 69 | once(this, this.markAsFocuedOut); 70 | } 71 | 72 | @action 73 | markAsFocuedOut() { 74 | this.hasFocusedOut = true; 75 | } 76 | 77 | get canValidate() { 78 | return this.hasFocusedOut || !this.args.builder.isValid; 79 | } 80 | } 81 | 82 | class ConfigProxy { 83 | get value() { 84 | return this.content.value; 85 | } 86 | set value(v) { 87 | this.content.value = v; 88 | } 89 | 90 | get inputElementId() { 91 | return this.content.inputElementId; 92 | } 93 | get name() { 94 | return this.content.name; 95 | } 96 | get type() { 97 | return this.content.type; 98 | } 99 | get texts() { 100 | return this.content.texts; 101 | } 102 | get validations() { 103 | return this.content.validations; 104 | } 105 | get canValidate() { 106 | return this.content.canValidate; 107 | } 108 | @dependentKeyCompat 109 | get disabled() { 110 | return this.content.disabled; 111 | } 112 | 113 | constructor(content) { 114 | this.content = content; 115 | 116 | A(Object.keys(content.args)) 117 | .removeObjects([ 118 | 'attr', 119 | 'as', 120 | 'builder', 121 | 'canValidate', 122 | 'disabled', 123 | 'hint', 124 | 'inputElementId', 125 | 'label', 126 | 'name', 127 | 'placeholder', 128 | 'type', 129 | 'texts', 130 | 'validations', 131 | ]) 132 | .forEach((key) => 133 | defineProperty(this, key, { 134 | get() { 135 | return this.content.args[key]; 136 | }, 137 | }) 138 | ); 139 | } 140 | } 141 | 142 | class TextProxy { 143 | constructor(context) { 144 | this.content = context.args; 145 | this.translationService = context.translationService; 146 | return new Proxy(this, { 147 | get(self, key) { 148 | if (key in self) { 149 | return self[key]; 150 | } 151 | if (self.exists(key)) { 152 | return self.content[key] || self.translate(key.toString()); 153 | } 154 | return undefined; 155 | }, 156 | }); 157 | } 158 | 159 | get label() { 160 | if (this.exists('label')) { 161 | return ( 162 | this.content.label || 163 | this.translate('attribute') || 164 | humanize(this.content.attr) 165 | ); 166 | } 167 | return undefined; 168 | } 169 | 170 | exists(type) { 171 | return this.content[type] !== false; 172 | } 173 | 174 | translate(type) { 175 | return this.translationService.t( 176 | this.content.builder.translationKey, 177 | type, 178 | this.content.attr 179 | ); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /addon/src/components/form-builder/label.hbs: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /addon/src/components/form-builder/label.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class Label extends Component { 5 | @service('formBuilderTranslations') translationService; 6 | 7 | get requiredText() { 8 | this.translationService.locale; 9 | return this.translationService.t('formBuilder.isRequired') || 'Required'; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /addon/src/components/form-builder/submit.hbs: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /addon/src/components/form-builder/submit.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { inject as service } from '@ember/service'; 3 | 4 | export default class Submit extends Component { 5 | @service('formBuilderTranslations') translationService; 6 | 7 | get builder() { 8 | return ( 9 | (this.args.builder && this.args.builder.builder) || this.args.builder 10 | ); 11 | } 12 | 13 | get text() { 14 | return ( 15 | this.args.text || 16 | this.translationService.t( 17 | this.builder && this.builder.translationKey, 18 | 'action', 19 | 'submit' 20 | ) || 21 | 'Save' 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /addon/src/components/input-wrappers/default.hbs: -------------------------------------------------------------------------------- 1 | {{#let (and @config.canValidate @config.validations.errors) as |isInvalid|}} 2 |
3 | {{#if @config.texts.label}} 4 | <@labelComponent /> 5 | {{/if}} 6 |
7 | <@inputComponent 8 | class={{concat "form-control" (if isInvalid " is-invalid")}} 9 | /> 10 |
11 | {{#if @config.texts.hint}} 12 | {{@config.texts.hint}} 13 | {{/if}} 14 | {{#if isInvalid}} 15 |
16 | {{#each @config.validations.errors as |error i|~}} 17 | {{if i ", "}}{{error}} 18 | {{~/each}} 19 |
20 | {{/if}} 21 |
22 | {{/let}} 23 | -------------------------------------------------------------------------------- /addon/src/components/input-wrappers/inline.hbs: -------------------------------------------------------------------------------- 1 | {{#let (and @config.canValidate @config.validations.errors) as |isInvalid|}} 2 |
3 | <@inputComponent 4 | class={{concat "form-check-input" (if isInvalid " is-invalid")}} 5 | /> 6 | <@labelComponent class="form-check-label" /> 7 | {{#if @config.texts.hint}} 8 | {{@config.texts.hint}} 9 | {{/if}} 10 | {{#if isInvalid}} 11 |
12 | {{#each @config.validations.errors as |error i|~}} 13 | {{if i ", "}}{{error}} 14 | {{~/each}} 15 |
16 | {{/if}} 17 |
18 | {{/let}} 19 | -------------------------------------------------------------------------------- /addon/src/components/inputs/boolean-input.hbs: -------------------------------------------------------------------------------- 1 | {{!-- template-lint-disable no-autofocus-attribute no-positive-tabindex --}} 2 | 3 | 22 | -------------------------------------------------------------------------------- /addon/src/components/inputs/boolean-input.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action, set } from '@ember/object'; 3 | 4 | export default class extends Component { 5 | @action 6 | handleChange(e) { 7 | set(this, 'args.config.value', e.target.checked); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /addon/src/components/inputs/checkbox-option.hbs: -------------------------------------------------------------------------------- 1 |
2 | 14 |
15 | -------------------------------------------------------------------------------- /addon/src/components/inputs/checkbox-option.js: -------------------------------------------------------------------------------- 1 | import SelectOption from './select-option'; 2 | import { guidFor } from '@ember/object/internals'; 3 | 4 | export default class CheckboxOption extends SelectOption { 5 | inputElementId = guidFor(Math.random()); 6 | } 7 | -------------------------------------------------------------------------------- /addon/src/components/inputs/checkboxes-input.hbs: -------------------------------------------------------------------------------- 1 |
5 | 10 | {{#each base.collection as |option|}} 11 | 21 | {{/each}} 22 | 23 |
24 | -------------------------------------------------------------------------------- /addon/src/components/inputs/checkboxes-input.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action } from '@ember/object'; 3 | 4 | export default class CheckboxesInput extends Component { 5 | @action 6 | registerWrapper(el) { 7 | this.wrapper = el; 8 | } 9 | 10 | @action 11 | getSelectedIndices() { 12 | var indices = []; 13 | this.wrapper.querySelectorAll('input').forEach(function (input, i) { 14 | if (input.checked) { 15 | indices.push(i); 16 | } 17 | }); 18 | return indices; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /addon/src/components/inputs/collection-input.hbs: -------------------------------------------------------------------------------- 1 | {{!-- template-lint-disable no-autofocus-attribute no-positive-tabindex --}} 2 | 3 | {{#if (has-block)}} 4 | {{yield (hash 5 | handleChange=this.handleChange 6 | setDefaultValue=this.setDefaultValue 7 | multiple=this.multiple 8 | value=this.value 9 | collection=this.resolvedCollection 10 | )}} 11 | {{else}} 12 | 40 | {{/if}} 41 | -------------------------------------------------------------------------------- /addon/src/components/inputs/collection-input.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { A, isArray } from '@ember/array'; 3 | import { action } from '@ember/object'; 4 | import { next } from '@ember/runloop'; 5 | import { tracked } from '@glimmer/tracking'; 6 | import { createCache, getValue } from '@glimmer/tracking/primitives/cache'; 7 | 8 | class CollectionPromise { 9 | @tracked content; 10 | 11 | constructor(promise) { 12 | this.waitForContent(promise); 13 | } 14 | 15 | async waitForContent(promise) { 16 | this.content = await promise; 17 | } 18 | } 19 | 20 | export default class CollectionInput extends Component { 21 | get value() { 22 | return this.args.config.value; 23 | } 24 | 25 | #collectionPromise = createCache(() => { 26 | return new CollectionPromise(this.args.config.collection); 27 | }); 28 | get collectionPromise() { 29 | return getValue(this.#collectionPromise); 30 | } 31 | 32 | get resolvedCollection() { 33 | return (this.collectionPromise.content || []).map((option) => { 34 | if (typeof option === 'object') { 35 | return option; 36 | } else { 37 | return { 38 | value: option, 39 | label: option, 40 | content: option, 41 | }; 42 | } 43 | }); 44 | } 45 | 46 | @action 47 | setDefaultValue(element) { 48 | next(this, this.handleChange, { currentTarget: element }); 49 | } 50 | 51 | @action 52 | handleChange(e) { 53 | if (this.args.getSelectedIndices) { 54 | this._setSelection(this.args.getSelectedIndices()); 55 | } else { 56 | let selected = Array.prototype.slice.call( 57 | e.currentTarget.querySelectorAll('option:checked') 58 | ); 59 | this._setSelection(selected.map((el) => el.index)); 60 | } 61 | } 62 | 63 | async _setSelection(indicies) { 64 | await this.collectionPromise; 65 | let newValues = A(A(this.resolvedCollection).objectsAt(indicies)).mapBy( 66 | 'content' 67 | ); 68 | let value = this.value; 69 | 70 | if (this.multiple) { 71 | A(value).replace(0, this.value.length, newValues); 72 | } else if (!this.isDestroyed) { 73 | this.args.config.value = newValues[0]; 74 | } 75 | } 76 | 77 | get multiple() { 78 | return isArray(this.value); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /addon/src/components/inputs/date-input.js: -------------------------------------------------------------------------------- 1 | import StringInput from './string-input'; 2 | import { action } from '@ember/object'; 3 | 4 | export default class DateInput extends StringInput { 5 | type = 'date'; 6 | 7 | get value() { 8 | return formatDate(this.args.config.value); 9 | } 10 | 11 | @action 12 | handleChange(e) { 13 | let value = e.target.value; 14 | if (value.length === 0) { 15 | this.args.config.value = undefined; 16 | } else { 17 | let timestamp = Date.parse(value + 'T00:00Z'); 18 | if (isNaN(timestamp)) { 19 | return value; 20 | } else { 21 | var date = new Date(timestamp); 22 | this.args.config.value = date; 23 | } 24 | } 25 | } 26 | } 27 | 28 | function pad(number, length = 2) { 29 | let result = number.toString(); 30 | length = length - result.length; 31 | if (length > 0) { 32 | return Array(length + 1).join('0') + result; 33 | } 34 | return result; 35 | } 36 | 37 | function formatDate(date) { 38 | if (date && date.getFullYear && date.getMonth && date.getDate) { 39 | return ( 40 | pad(date.getFullYear(), 4) + 41 | '-' + 42 | pad(date.getMonth() + 1) + 43 | '-' + 44 | pad(date.getDate()) 45 | ); 46 | } else { 47 | return null; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /addon/src/components/inputs/email-input.js: -------------------------------------------------------------------------------- 1 | import StringInput from './string-input'; 2 | 3 | export default class EmailInputComponent extends StringInput { 4 | type = 'email'; 5 | } 6 | -------------------------------------------------------------------------------- /addon/src/components/inputs/number-input.js: -------------------------------------------------------------------------------- 1 | import StringInput from './string-input'; 2 | import { action } from '@ember/object'; 3 | import { isPresent } from '@ember/utils'; 4 | 5 | export default class NumberInputComponent extends StringInput { 6 | type = 'number'; 7 | 8 | get value() { 9 | return this.args.config.value; 10 | } 11 | 12 | @action 13 | handleChange(e) { 14 | let value = e.target.value; 15 | value = parseFloat(value); 16 | value = isNaN(value) ? undefined : value; 17 | this.args.config.value = value; 18 | } 19 | 20 | get validations() { 21 | return this.args.config.validations?.number; 22 | } 23 | 24 | get step() { 25 | return this.validations?.integer ? 1 : 0.01; 26 | } 27 | 28 | get min() { 29 | var n = this.validations?.gt; 30 | if (isPresent(n)) { 31 | return n * 1 + this.step; 32 | } else { 33 | return this.validations?.gte; 34 | } 35 | } 36 | 37 | get max() { 38 | var n = this.validations?.lt; 39 | if (isPresent(n)) { 40 | return n * 1 - this.step; 41 | } else { 42 | return this.validations?.lte; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /addon/src/components/inputs/password-input.js: -------------------------------------------------------------------------------- 1 | import StringInput from './string-input'; 2 | 3 | export default class PasswordInputComponent extends StringInput { 4 | type = 'password'; 5 | } 6 | -------------------------------------------------------------------------------- /addon/src/components/inputs/select-option.hbs: -------------------------------------------------------------------------------- 1 | {{yield this.isSelected}} 2 | {{#if (not (has-block))}} 3 | 10 | {{/if}} 11 | -------------------------------------------------------------------------------- /addon/src/components/inputs/select-option.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { isArray, A } from '@ember/array'; 3 | 4 | export default class SelectOption extends Component { 5 | get isSelected() { 6 | let key = this.args.key; 7 | if (isArray(this.args.selectedValue)) { 8 | return !!A(this.args.selectedValue) 9 | .map((obj) => unwrap(obj, key)) 10 | .includes(unwrap(this.args.content.content, key)); 11 | } else { 12 | return ( 13 | unwrap(this.args.selectedValue, key) === 14 | unwrap(this.args.content.content, key) 15 | ); 16 | } 17 | } 18 | } 19 | 20 | function unwrap(obj, key) { 21 | if (key) { 22 | return (obj || {})[key]; 23 | } else { 24 | return obj; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /addon/src/components/inputs/string-input.hbs: -------------------------------------------------------------------------------- 1 | {{!-- template-lint-disable no-autofocus-attribute no-positive-tabindex --}} 2 | 3 | 34 | -------------------------------------------------------------------------------- /addon/src/components/inputs/string-input.js: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action } from '@ember/object'; 3 | 4 | export default class StringInputComponent extends Component { 5 | type = 'text'; 6 | 7 | get value() { 8 | return this.args.config.value; 9 | } 10 | 11 | @action 12 | handleChange(e) { 13 | this.args.config.value = e.target.value; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /addon/src/components/inputs/tel-input.js: -------------------------------------------------------------------------------- 1 | import StringInput from './string-input'; 2 | 3 | export default class TelInputComponent extends StringInput { 4 | type = 'tel'; 5 | } 6 | -------------------------------------------------------------------------------- /addon/src/components/inputs/text-input.hbs: -------------------------------------------------------------------------------- 1 | {{!-- template-lint-disable no-autofocus-attribute no-positive-tabindex --}} 2 | 3 |