├── .github
└── FUNDING.yml
├── .gitignore
├── .prettierrc
├── LICENSE
├── README.md
├── app-extension
├── .npmignore
├── README.md
├── package.json
└── src
│ ├── boot
│ └── register.js
│ └── index.js
├── breaking changes.md
├── jsconfig.json
├── package-lock.json
└── ui
├── .npmignore
├── README.md
├── build
├── config.js
├── entry
│ ├── index.common.js
│ ├── index.esm.js
│ └── index.umd.js
├── index.js
├── script.app-ext.js
├── script.clean.js
├── script.css.js
├── script.javascript.js
├── script.open-umd.js
└── utils.js
├── dev
├── .editorconfig
├── .firebase
│ └── hosting.ZGlzdC9zcGE.cache
├── .firebaserc
├── .gitignore
├── .postcssrc.js
├── README.md
├── babel.config.js
├── firebase.json
├── package-lock.json
├── package.json
├── quasar.conf.js
├── quasar.extensions.json
└── src
│ ├── App.vue
│ ├── assets
│ └── .gitkeep
│ ├── boot
│ ├── register.js
│ └── vueComponents.js
│ ├── components
│ ├── .gitkeep
│ ├── InfoBoxWrapper.vue
│ ├── PrimaryColorPicker.vue
│ ├── Snarkdown.vue
│ ├── Template.vue
│ └── showMoreWrapper.vue
│ ├── css
│ ├── app.sass
│ ├── colors.sass
│ ├── quasar.variables.sass
│ └── typography.sass
│ ├── helpers
│ ├── conversion.js
│ ├── pageUtils.js
│ ├── parseCodeForPrint.js
│ └── schemaBuilders.js
│ ├── index.template.html
│ ├── layouts
│ └── MyLayout.vue
│ ├── new-examples
│ └── Basics.vue
│ ├── pages
│ ├── EasyFieldDemo.vue
│ ├── EasyFormDemo.vue
│ ├── Index.vue
│ └── NewDemo.vue
│ ├── router
│ ├── index.js
│ └── routes.js
│ ├── schemas
│ ├── examples
│ │ ├── EfBtn.js
│ │ ├── EfDiv.js
│ │ ├── EfMiniForm.js
│ │ ├── QInput.js
│ │ ├── QSelectModel.js
│ │ ├── actionButtons.js
│ │ ├── advanced.js
│ │ ├── basics.js
│ │ ├── computedFields.js
│ │ ├── computedFields2.js
│ │ ├── computedFields3.js
│ │ ├── evaluatedProps1.js
│ │ ├── evaluatedProps2.js
│ │ ├── evaluatedProps3.js
│ │ ├── evaluatedProps4.js
│ │ ├── events1.js
│ │ ├── events2.js
│ │ ├── index.js
│ │ ├── nestedData.js
│ │ ├── responsiveStyle.js
│ │ ├── validation.js
│ │ └── validation2.js
│ └── pages
│ │ ├── QSelectModel.js
│ │ ├── actionButtons.js
│ │ ├── advanced.js
│ │ ├── basics.js
│ │ ├── computedFields.js
│ │ ├── evaluatedProps.js
│ │ ├── events.js
│ │ ├── index.js
│ │ ├── nestedData.js
│ │ ├── responsiveStyle.js
│ │ └── validation.js
│ └── statics
│ ├── app-logo-128x128.png
│ └── icons
│ ├── apple-icon-120x120.png
│ ├── apple-icon-152x152.png
│ ├── apple-icon-167x167.png
│ ├── apple-icon-180x180.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── favicon.ico
│ ├── icon-128x128.png
│ ├── icon-192x192.png
│ ├── icon-256x256.png
│ ├── icon-384x384.png
│ ├── icon-512x512.png
│ ├── ms-icon-144x144.png
│ └── safari-pinned-tab.svg
├── package-lock.json
├── package.json
├── src
├── components
│ ├── EasyField.vue
│ ├── EasyForm.vue
│ ├── fields
│ │ ├── EfBtn.vue
│ │ ├── EfDiv.vue
│ │ ├── EfMiniForm.vue
│ │ └── sharedProps.js
│ └── sharedProps.js
├── helpers
│ ├── dateHelpers.js
│ ├── flattenPerSchema.js
│ ├── focusIfInputEl.js
│ ├── parseFieldValue.js
│ ├── validation.js
│ └── validation.ts
├── index.js
├── index.sass
├── margin-padding.sass
├── meta
│ ├── dependencyMap.js
│ ├── lang.js
│ ├── lang.ts
│ └── quasarPropsJson.js
└── types
│ └── index.ts
├── test
├── flattenPerSchema.js
├── index.js
└── parseFieldValue.js
└── umd-test.html
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: mesqueeb
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .thumbs.db
3 | node_modules
4 | dist
5 | yarn.lock
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Editor directories and files
11 | .idea
12 | .vscode
13 | *.suo
14 | *.ntvs*
15 | *.njsproj
16 | *.sln
17 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 100,
3 | "tabWidth": 2,
4 | "singleQuote": true,
5 | "trailingComma": "es5",
6 | "semi": false,
7 | "bracketSpacing": true,
8 | "quoteProps": "consistent"
9 | }
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Luca Ban - Mesqueeb
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Quasar Easy Forms 📮
2 |
3 | New version:
4 |
5 | # Blitzar released! 🎉
6 |
7 | Quasar Easy Forms & Tables was upgraded to become much more powerful!
8 |
9 | The new version is called **Blitzar**
10 |
11 | - see blog post here: https://lucaban.medium.com/better-faster-vue-forms-with-blitzar-a0d71258a3bb
12 | - see documentation here: https://blitzar.cycraft.co
13 | - see the upgrade guide here: https://github.com/CyCraft/blitzar/releases/tag/v0.0.14
14 |
15 | # Support
16 |
17 | If this helped you in any way, you can contribute to the package's long term survival by supporting me:
18 |
19 | ### [💜 Support my open-source work on GitHub](https://github.com/sponsors/mesqueeb)
20 |
21 | Be sure to check out my sponsor page, I have a lot of open-source packages that might help you!
22 |
23 | (GitHub currently **doubles your support**! So if you support me with $10/mo, I will $20 instead! They're alchemists 🦾😉)
24 |
25 | Thank you so much!!!
26 |
27 | # License
28 | MIT (c) Luca Ban - Mesqueeb
29 |
--------------------------------------------------------------------------------
/app-extension/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .thumbs.db
3 | yarn.lock
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Editor directories and files
9 | .idea
10 | .vscode
11 | *.suo
12 | *.ntvs*
13 | *.njsproj
14 | *.sln
15 | .editorconfig
16 | .eslintignore
17 | .eslintrc.js
18 |
--------------------------------------------------------------------------------
/app-extension/README.md:
--------------------------------------------------------------------------------
1 | Quasar Easy Forms 📮
2 |
3 | New version:
4 |
5 | # Blitzar released! 🎉
6 |
7 | Quasar Easy Forms & Tables was upgraded to become much more powerful!
8 |
9 | The new version is called **Blitzar**
10 |
11 | - see blog post here: https://lucaban.medium.com/better-faster-vue-forms-with-blitzar-a0d71258a3bb
12 | - see documentation here: https://blitzar.cycraft.co
13 | - see the upgrade guide here: https://github.com/CyCraft/blitzar/releases/tag/v0.0.14
14 |
15 | # Support
16 |
17 | If this helped you in any way, you can contribute to the package's long term survival by supporting me:
18 |
19 | ### [💜 Support my open-source work on GitHub](https://github.com/sponsors/mesqueeb)
20 |
21 | Be sure to check out my sponsor page, I have a lot of open-source packages that might help you!
22 |
23 | (GitHub currently **doubles your support**! So if you support me with $10/mo, I will $20 instead! They're alchemists 🦾😉)
24 |
25 | Thank you so much!!!
26 |
27 | # License
28 | MIT (c) Luca Ban - Mesqueeb
29 |
--------------------------------------------------------------------------------
/app-extension/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quasar-app-extension-easy-forms",
3 | "version": "2.4.2",
4 | "description": "Easily generate forms by only defining a \"schema\" object.",
5 | "author": "Luca Ban - Mesqueeb",
6 | "license": "MIT",
7 | "main": "src/index.js",
8 | "repository": {
9 | "type": "git",
10 | "url": "https://github.com/mesqueeb/quasar-ui-easy-forms"
11 | },
12 | "bugs": "https://github.com/mesqueeb/quasar-ui-easy-forms/issues",
13 | "homepage": "https://quasar-easy-forms.web.app",
14 | "dependencies": {
15 | "quasar-ui-easy-forms": "^2.4.1"
16 | },
17 | "engines": {
18 | "node": ">= 8.9.0",
19 | "npm": ">= 5.6.0",
20 | "yarn": ">= 1.6.0"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app-extension/src/boot/register.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VuePlugin from 'quasar-ui-easy-forms'
3 |
4 | Vue.use(VuePlugin)
5 |
--------------------------------------------------------------------------------
/app-extension/src/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Quasar App Extension index/runner script
3 | * (runs on each dev/build)
4 | *
5 | * Docs: https://quasar.dev/app-extensions/development-guide/index-api
6 | * API: https://github.com/quasarframework/quasar/blob/master/app/lib/app-extension/IndexAPI.js
7 | */
8 |
9 | function extendConf (conf) {
10 | // register our boot file
11 | conf.boot.push('~quasar-app-extension-easy-forms/src/boot/register.js')
12 |
13 | // make sure app extension files & ui package gets transpiled
14 | conf.build.transpileDependencies.push(/quasar-app-extension-easy-forms[\\/]src/)
15 |
16 | // make sure the stylesheet goes through webpack to avoid SSR issues
17 | conf.css.push('~quasar-ui-easy-forms/src/index.sass')
18 | }
19 |
20 | module.exports = function (api) {
21 | // Quasar compatibility check; you may need
22 | // hard dependencies, as in a minimum version of the "quasar"
23 | // package or a minimum version of "@quasar/app" CLI
24 | api.compatibleWith('quasar', '^1.1.1')
25 | api.compatibleWith('@quasar/app', '^1.1.0')
26 |
27 | // Uncomment the line below if you provide a JSON API for your component
28 | // api.registerDescribeApi('EasyForm', '~quasar-ui-easy-forms/src/component/EasyForm.json')
29 |
30 | // We extend /quasar.conf.js
31 | api.extendQuasarConf(extendConf)
32 | }
33 |
--------------------------------------------------------------------------------
/breaking changes.md:
--------------------------------------------------------------------------------
1 |
2 | In version 1.0 I was able to greatly simplify easy-forms. There are no wrapper components needed for every field. You can directly use Quasar components or ANY component you create yourself, without requiring any setup!
3 |
4 | Here is a list of all breaking changes.
5 |
6 | # Breaking changes
7 |
8 | ## actionButtons
9 |
10 | Action buttons now default to an empty array.
11 |
12 | **why:**
13 | It's better to be explicited about the components that should be rendered. It's easier to understand that there will be buttons shown at the top of an EasyForm if you see that they are actually passed.
14 |
15 | It's good that there are buttons pre-made for us, but it's better that these are explicitly added.
16 |
17 | **how to update code:**
18 | Anywhere you have not defined action buttons you can pass the previous default ones if you like: `['archive', 'cancel', 'edit', 'save']`
19 |
20 | ## fieldType
21 |
22 | EasyForms now uses `component` instead of `fieldType`.
23 | The "component" field can be passed any component name (string) which is globally registered, OR you can pass a component options object directly.
24 |
25 | It's very powerful because now you can have EasyForms use _any_ Quasar component by eg. `component: 'QBtn'`. You can also define all your own custom components and use those, as long as you register them globally via `Vue.component()`
26 |
27 | **why:**
28 | EasyForms used to provide a wrapper component for every single component that was usable. This becomes very unwieldly very fast. I refactored the code to be able to provide plain Quasar components.
29 |
30 | I still export the original wrapper components I did via the field names with `Ef` in front. Eg. `fieldType: 'input'` can now be used via `component: 'EfInput'`.
31 |
32 | **how to update code:**
33 |
34 | 1. replace `fieldType: 'input'` with `component: 'EfInput'`
35 | 2. repeat for all field types
36 |
37 | ## mode & formMode
38 |
39 | EasyForm has a `mode` which can be 'view', 'edit', 'add' and 'raw'.
40 | EasyField used to receive this prop as `formMode` but now receives it as just `mode`.
41 |
42 | An individual field could have a different 'mode' by passing `readonly: true` or `rawValue: true`.
43 | Now when you want to set the 'mode' of a single field, you should just set the mode the same as EasyForms: `mode: 'raw'`
44 |
45 | Another change is that EasyForms used to have mode="view" by default, but now it defaults to mode="edit"
46 |
47 | **why:**
48 | Unified syntax. A single prop called "mode" can be set globally at the EasyForm or locally at EasyField.
49 | I went for "edit" instead of "view" by default because then fields are editable by default and thus closest to their intended state. I think this will cause less confusion.
50 |
51 | **how to update code:**
52 |
53 | 1. replace `formMode` (in evaluated props) with just `mode`
54 | 2. replace `rawValue: true` with `mode: 'raw'`
55 | 3. replace `readonly: true` with `mode: 'view'`
56 | 4. add `mode: 'view'` or `mode="view"` to EasyForms without mode set
57 |
58 | ## disable
59 |
60 | `disable` used to be ignored in the case `readonly` is true.
61 | `disable` now is not modified as such any more.
62 |
63 | **why**:
64 | To avoid magic things happening without a strong reasoning. It will make working with EasyForms more predictable and thus easier to reason about.
65 |
66 | **how to update code:**
67 | If you have fields you set to `disable: true` but want it to be `false` when `readonly: true` you can do:
68 |
69 | ```js
70 | // change
71 | {
72 | disable: true,
73 | }
74 | // to
75 | {
76 | disable: (val, {mode}) => mode !== 'view',
77 | }
78 | ```
79 |
80 | ## evaluatedProps
81 |
82 | An EasyField used to execute any prop which was a function to allow for these "Evaluated Props". Now you need to be explicit about which props you want to use as "Evaluated Props".
83 |
84 | **why:**
85 | Because now any component can a "field" (EasyForm doesn't use its own field wrappers). We cannot anticipate which props of these fields can accept functions.
86 |
87 | **how to update code:**
88 |
89 | The new prop called "evaluatedProps" defaults to an array with some prop names by default.
90 | If your EasyForms only use Evaluated Props for the following props, you don't need to do anything:
91 |
92 | ```js
93 | ;[
94 | 'component',
95 | 'showCondition',
96 | 'label',
97 | 'subLabel',
98 | 'required',
99 | 'rules',
100 | 'fieldStyle',
101 | 'fieldClasses',
102 | 'componentStyle',
103 | 'componentClasses',
104 | 'disable',
105 | 'events',
106 | 'lang',
107 | ]
108 | ```
109 |
110 | If you have other props in your schemas that were "Evaluated Props" you will have to manually pass an array with _all_ props that you use as evaluated props: `evaluatedProps: ['myProp', 'subLabel']` etc.
111 |
112 | This can be defined on the EasyForm level or individually on the EasyField level.
113 |
114 | ## format
115 |
116 | The prop called `format` in which you could pass a function is now renamed to `parseValue`.
117 | This was renamed for clarity because it's the opposite from `parseInput`. Now there is `parseValue` and `parseInput`. Much better!!
118 |
119 | Just search & replace ;)
120 |
121 | ## innerClasses, innerStyle
122 |
123 | Both `innerClasses` and `innerStyle` are renamed to `componentClasses` & `componentStyle` to make it clear that these are applied directly to the component. Just search & replace ;)
124 |
125 | ## CSS
126 |
127 | The class `.easy-field__field` was renamed to `.easy-field__component`. Just search & replace ;)
128 |
129 | ## externalLabels
130 |
131 | The prop called `externalLabels` was `true` by default and meant that labels were shown in the EasyField and not the component.
132 |
133 | Now when passing `internalLabels: true` it will show the labels as part of the component.
134 |
135 | So instead of `externalLabels: false` you need to pass `internalLabels: true` instead.
136 |
137 | Also check out the new prop: `internalErrors`
138 |
139 | ## Changes to fields (check all!!)
140 |
141 | I removed most fields, because they were _just_ wrapper components without much additive value. Also, the focus of the library is now only on the form generation, not providing a bunch of fields.
142 |
143 | You should update your code to use Quasar components directly instead. See below:
144 |
145 | ## Fields I deleted:
146 |
147 | ### title / EfTitle:
148 |
149 | ```js
150 | // Before:
151 | { fieldType: 'title', label: 'my title' }
152 | // v1.0:
153 | {
154 | // don't specify any component
155 | label: 'my title',
156 | span: true,
157 | fieldClasses: 'easy-field--title',
158 | }
159 | ```
160 |
161 | You can make title fields prettier via CSS. Eg.
162 |
163 | ```sass
164 | .easy-field--title .easy-field__label
165 | font-weight: 600
166 | ```
167 |
168 | ### Others:
169 |
170 | ```js
171 | // Before:
172 | { fieldType: 'btnToggle' }
173 | { fieldType: 'img' }
174 | { fieldType: 'input' }
175 | { fieldType: 'markdown' }
176 | { fieldType: 'range' }
177 | { fieldType: 'select' }
178 | { fieldType: 'slider' }
179 | { fieldType: 'toggle' }
180 |
181 | // v1.0:
182 | { component: 'QBtnToggle', spread: true }
183 | { component: 'QImg' }
184 | { component: 'QInput' }
185 | { component: 'QMarkdown', noContainer: true, /* this is a bug fix */, noLineNumbers: true }
186 | { component: 'QRange' }
187 | { component: 'QSelect' }
188 | { component: 'QSlider' }
189 | { component: 'QToggle', default: false }
190 | ```
191 |
192 | Some fields might have small differences in behaviour from my previous wrapper components. However, you can check the [source code of my wrapper components here](https://github.com/mesqueeb/quasar-ui-easy-forms/tree/a6b5c9626e6cf172a42f588a361e3b665e9a840f/ui/src/components/fields) and copy the wrapper components to your project if you want.
193 |
194 | No equivalents:
195 |
196 | ```js
197 | // Before:
198 | { fieldType: 'inputDate' }
199 | { fieldType: 'link' }
200 | { fieldType: 'pdf' }
201 | { fieldType: 'uploaderFirebase' }
202 | { fieldType: 'video' }
203 |
204 | // v1.0
205 | // these don't exist anymore.
206 | ```
207 |
208 | You can copy my [old source code](https://github.com/mesqueeb/quasar-ui-easy-forms/tree/a6b5c9626e6cf172a42f588a361e3b665e9a840f/ui/src/components/fields).
209 |
210 | It's better to have these components defined locally anyway, then it's much more flexible when you need new functionality.
211 |
212 | ## Fields I kept as is:
213 |
214 | ```jsack
215 | // Before:
216 | { fieldType: 'btn' }
217 | { fieldType: 'div' }
218 | { fieldType: 'form' }
219 | // v1.0:
220 | { component: 'EfBtn' } // because this is used in the action-buttons
221 | { component: 'EfDiv' }
222 | { component: 'EfMiniForm' }
223 | ```
224 |
225 | I updated the name to EfMiniForm to make it easier to understand that this is a mini form, not a full fledged EasyForm.
226 |
227 | ## formDataNested
228 |
229 | `formDataNested` was renamed to just `formData`. Just search & replace ;)
230 |
231 |
232 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "compilerOptions": {
4 | // This must be specified if "paths" is set
5 | "baseUrl": ".",
6 | // Relative to "baseUrl"
7 | "paths": {
8 | "ui/*": ["src/*"]
9 | }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "lockfileVersion": 1
3 | }
4 |
--------------------------------------------------------------------------------
/ui/.npmignore:
--------------------------------------------------------------------------------
1 | /build
2 | /dev
3 | umd-test.html
4 |
5 | .DS_Store
6 | .thumbs.db
7 | yarn.lock
8 | npm-debug.log*
9 | yarn-debug.log*
10 | yarn-error.log*
11 |
12 | # Editor directories and files
13 | .idea
14 | .vscode
15 | *.suo
16 | *.ntvs*
17 | *.njsproj
18 | *.sln
19 | .editorconfig
20 | .eslintignore
21 | .eslintrc.js
22 |
--------------------------------------------------------------------------------
/ui/README.md:
--------------------------------------------------------------------------------
1 | Quasar Easy Forms 📮
2 |
3 | New version:
4 |
5 | # Blitzar released! 🎉
6 |
7 | Quasar Easy Forms & Tables was upgraded to become much more powerful!
8 |
9 | The new version is called **Blitzar**
10 |
11 | - see blog post here: https://lucaban.medium.com/better-faster-vue-forms-with-blitzar-a0d71258a3bb
12 | - see documentation here: https://blitzar.cycraft.co
13 | - see the upgrade guide here: https://github.com/CyCraft/blitzar/releases/tag/v0.0.14
14 |
15 | # Support
16 |
17 | If this helped you in any way, you can contribute to the package's long term survival by supporting me:
18 |
19 | ### [💜 Support my open-source work on GitHub](https://github.com/sponsors/mesqueeb)
20 |
21 | Be sure to check out my sponsor page, I have a lot of open-source packages that might help you!
22 |
23 | (GitHub currently **doubles your support**! So if you support me with $10/mo, I will $20 instead! They're alchemists 🦾😉)
24 |
25 | Thank you so much!!!
26 |
27 | # License
28 | MIT (c) Luca Ban - Mesqueeb
29 |
--------------------------------------------------------------------------------
/ui/build/config.js:
--------------------------------------------------------------------------------
1 | const { name, author, version } = require('../package.json')
2 | const year = (new Date()).getFullYear()
3 |
4 | module.exports = {
5 | name,
6 | version,
7 | banner:
8 | '/*!\n' +
9 | ' * ' + name + ' v' + version + '\n' +
10 | ' * (c) ' + year + ' ' + author + '\n' +
11 | ' * Released under the MIT License.\n' +
12 | ' */\n'
13 | }
14 |
--------------------------------------------------------------------------------
/ui/build/entry/index.common.js:
--------------------------------------------------------------------------------
1 | import Plugin from '../../src/index'
2 |
3 | export default Plugin
4 |
--------------------------------------------------------------------------------
/ui/build/entry/index.esm.js:
--------------------------------------------------------------------------------
1 | import Plugin from '../../src/index'
2 |
3 | export default Plugin
4 | export * from '../../src/index'
5 |
--------------------------------------------------------------------------------
/ui/build/entry/index.umd.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Plugin from '../../src/index'
3 |
4 | Vue.use(Plugin)
5 |
6 | export * from '../../src/index'
7 |
--------------------------------------------------------------------------------
/ui/build/index.js:
--------------------------------------------------------------------------------
1 | process.env.NODE_ENV = 'production'
2 |
3 | const parallel = require('os').cpus().length > 1
4 | const runJob = parallel ? require('child_process').fork : require
5 | const { join } = require('path')
6 | const { createFolder } = require('./utils')
7 | const { green, blue } = require('chalk')
8 |
9 | console.log()
10 |
11 | require('./script.app-ext.js').syncAppExt()
12 | require('./script.clean.js')
13 |
14 | console.log(` 📦 Building ${green('v' + require('../package.json').version)}...${parallel ? blue(' [multi-threaded]') : ''}\n`)
15 |
16 | createFolder('dist')
17 |
18 | runJob(join(__dirname, './script.javascript'))
19 | runJob(join(__dirname, './script.css'))
20 |
--------------------------------------------------------------------------------
/ui/build/script.app-ext.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | path = require('path'),
4 | root = path.resolve(__dirname, '../..'),
5 | resolvePath = file => path.resolve(root, file),
6 | { blue } = require('chalk')
7 |
8 | const writeJson = function (file, json) {
9 | return fs.writeFileSync(file, JSON.stringify(json, null, 2) + '\n', 'utf-8')
10 | }
11 |
12 | module.exports.syncAppExt = function (both = true) {
13 | // make sure this project has an app-extension project
14 | const appExtDir = resolvePath('app-extension')
15 | if (!fs.existsSync(appExtDir)) {
16 | return
17 | }
18 |
19 | // make sure this project has an ui project
20 | const uiDir = resolvePath('ui')
21 | if (!fs.existsSync(uiDir)) {
22 | return
23 | }
24 |
25 | // get version and name from ui package.json
26 | const { name, version } = require(resolvePath(resolvePath('ui/package.json')))
27 |
28 | // read app-ext package.json
29 | const appExtFile = resolvePath('app-extension/package.json')
30 | let appExtJson = require(appExtFile),
31 | finished = false
32 |
33 | // sync version numbers
34 | if (both === true) {
35 | appExtJson.version = version
36 | }
37 |
38 | // check dependencies
39 | if (appExtJson.dependencies !== void 0) {
40 | if (appExtJson.dependencies[name] !== void 0) {
41 | appExtJson.dependencies[name] = '^' + version
42 | finished = true
43 | }
44 | }
45 | // check devDependencies, if not finished
46 | if (finished === false && appExtJson.devDependencies !== void 0) {
47 | if (appExtJson.devDependencies[name] !== void 0) {
48 | appExtJson.devDependencies[name] = '^' + version
49 | finished = true
50 | }
51 | }
52 |
53 | if (finished === true) {
54 | writeJson(appExtFile, appExtJson)
55 | console.log(` ⭐️ App Extension version ${blue(appExtJson.name)} synced.\n`)
56 | return
57 | }
58 |
59 | console.error(` App Extension version and dependency NOT synced.\n`)
60 | }
--------------------------------------------------------------------------------
/ui/build/script.clean.js:
--------------------------------------------------------------------------------
1 | var
2 | rimraf = require('rimraf'),
3 | path = require('path')
4 |
5 | rimraf.sync(path.resolve(__dirname, '../dist/*'))
6 | console.log(` 💥 Cleaned build artifacts.\n`)
7 |
--------------------------------------------------------------------------------
/ui/build/script.css.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const sass = require('node-sass')
3 | const postcss = require('postcss')
4 | const cssnano = require('cssnano')
5 | const rtl = require('postcss-rtl')
6 | const autoprefixer = require('autoprefixer')
7 |
8 | const buildConf = require('./config')
9 | const buildUtils = require('./utils')
10 |
11 | const postCssCompiler = postcss([ autoprefixer ])
12 | const postCssRtlCompiler = postcss([ rtl({}) ])
13 |
14 | const nano = postcss([
15 | cssnano({
16 | preset: ['default', {
17 | mergeLonghand: false,
18 | convertValues: false,
19 | cssDeclarationSorter: false,
20 | reduceTransforms: false
21 | }]
22 | })
23 | ])
24 |
25 | Promise
26 | .all([
27 | generate('src/index.sass', `dist/index`)
28 | ])
29 | .catch(e => {
30 | console.error(e)
31 | process.exit(1)
32 | })
33 |
34 | /**
35 | * Helpers
36 | */
37 |
38 | function resolve (_path) {
39 | return path.resolve(__dirname, '..', _path)
40 | }
41 |
42 | function generate (src, dest) {
43 | src = resolve(src)
44 | dest = resolve(dest)
45 |
46 | return new Promise((resolve, reject) => {
47 | sass.render({ file: src, includePaths: ['node_modules'] }, (err, result) => {
48 | if (err) {
49 | reject(err)
50 | return
51 | }
52 |
53 | resolve(result.css)
54 | })
55 | })
56 | .then(code => buildConf.banner + code)
57 | .then(code => postCssCompiler.process(code, { from: void 0 }))
58 | .then(code => {
59 | code.warnings().forEach(warn => {
60 | console.warn(warn.toString())
61 | })
62 | return code.css
63 | })
64 | .then(code => Promise.all([
65 | generateUMD(dest, code),
66 | postCssRtlCompiler.process(code, { from: void 0 })
67 | .then(code => generateUMD(dest, code.css, '.rtl'))
68 | ]))
69 | }
70 |
71 | function generateUMD (dest, code, ext = '') {
72 | return buildUtils.writeFile(`${dest}${ext}.css`, code, true)
73 | .then(code => nano.process(code, { from: void 0 }))
74 | .then(code => buildUtils.writeFile(`${dest}${ext}.min.css`, code.css, true))
75 | }
76 |
--------------------------------------------------------------------------------
/ui/build/script.javascript.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const rollup = require('rollup')
3 | const uglify = require('uglify-es')
4 | const buble = require('@rollup/plugin-buble')
5 | const json = require('@rollup/plugin-json')
6 | const nodeResolve = require('@rollup/plugin-node-resolve')
7 | const VuePlugin = require('rollup-plugin-vue')
8 | const commonjs = require('@rollup/plugin-commonjs')
9 |
10 | const buildConf = require('./config')
11 | const buildUtils = require('./utils')
12 |
13 | // include all dependencies as external by default
14 | const pkg = require('../package.json')
15 | const external = Object.keys(pkg.dependencies || [])
16 |
17 | const rollupPlugins = [
18 | commonjs(),
19 | nodeResolve({
20 | extensions: ['.js'],
21 | preferBuiltins: false,
22 | }),
23 | VuePlugin(),
24 | json(),
25 | buble({
26 | objectAssign: 'Object.assign',
27 | }),
28 | ]
29 |
30 | const builds = [
31 | {
32 | rollup: {
33 | input: {
34 | input: resolve(`entry/index.esm.js`),
35 | },
36 | output: {
37 | file: resolve(`../dist/index.esm.js`),
38 | format: 'es',
39 | },
40 | },
41 | build: {
42 | unminified: true,
43 | minified: false,
44 | },
45 | },
46 | {
47 | rollup: {
48 | input: {
49 | input: resolve(`entry/index.common.js`),
50 | },
51 | output: {
52 | file: resolve(`../dist/index.common.js`),
53 | format: 'cjs',
54 | },
55 | },
56 | build: {
57 | unminified: true,
58 | minified: false,
59 | },
60 | },
61 | {
62 | rollup: {
63 | input: {
64 | input: resolve(`entry/index.umd.js`),
65 | },
66 | output: {
67 | name: 'easyForms',
68 | file: resolve(`../dist/index.umd.js`),
69 | format: 'umd',
70 | },
71 | },
72 | build: {
73 | unminified: true,
74 | minified: true,
75 | minExt: true,
76 | },
77 | },
78 | ]
79 |
80 | build(builds)
81 |
82 | /**
83 | * Helpers
84 | */
85 |
86 | function resolve (_path) {
87 | return path.resolve(__dirname, _path)
88 | }
89 |
90 | function build (builds) {
91 | return Promise.all(builds.map(genConfig).map(buildEntry)).catch(buildUtils.logError)
92 | }
93 |
94 | function genConfig (opts) {
95 | Object.assign(opts.rollup.input, {
96 | plugins: rollupPlugins,
97 | external: external.concat(['vue', 'quasar']),
98 | })
99 |
100 | Object.assign(opts.rollup.output, {
101 | banner: buildConf.banner,
102 | globals: { vue: 'Vue', quasar: 'Quasar' },
103 | })
104 |
105 | return opts
106 | }
107 |
108 | function addExtension (filename, ext = 'min') {
109 | const insertionPoint = filename.lastIndexOf('.')
110 | return `${filename.slice(0, insertionPoint)}.${ext}${filename.slice(insertionPoint)}`
111 | }
112 |
113 | function buildEntry (config) {
114 | return rollup
115 | .rollup(config.rollup.input)
116 | .then(bundle => bundle.generate(config.rollup.output))
117 | .then(({ output }) => {
118 | const code =
119 | config.rollup.output.format === 'umd'
120 | ? injectVueRequirement(output[0].code)
121 | : output[0].code
122 |
123 | return config.build.unminified ? buildUtils.writeFile(config.rollup.output.file, code) : code
124 | })
125 | .then(code => {
126 | if (!config.build.minified) {
127 | return code
128 | }
129 |
130 | const minified = uglify.minify(code, {
131 | compress: {
132 | pure_funcs: ['makeMap'],
133 | },
134 | })
135 |
136 | if (minified.error) {
137 | return Promise.reject(minified.error)
138 | }
139 |
140 | return buildUtils.writeFile(
141 | config.build.minExt === true
142 | ? addExtension(config.rollup.output.file)
143 | : config.rollup.output.file,
144 | buildConf.banner + minified.code,
145 | true
146 | )
147 | })
148 | .catch(err => {
149 | console.error(err)
150 | process.exit(1)
151 | })
152 | }
153 |
154 | function injectVueRequirement (code) {
155 | const index = code.indexOf(`Vue = Vue && Vue.hasOwnProperty('default') ? Vue['default'] : Vue`)
156 |
157 | if (index === -1) {
158 | return code
159 | }
160 |
161 | const checkMe = ` if (Vue === void 0) {
162 | console.error('[ Quasar ] Vue is required to run. Please add a script tag for it before loading Quasar.')
163 | return
164 | }
165 | `
166 |
167 | return code.substring(0, index - 1) + checkMe + code.substring(index)
168 | }
169 |
--------------------------------------------------------------------------------
/ui/build/script.open-umd.js:
--------------------------------------------------------------------------------
1 | const { resolve } = require('path')
2 | const open = require('open')
3 |
4 | open(
5 | resolve(__dirname, '../umd-test.html')
6 | )
7 |
--------------------------------------------------------------------------------
/ui/build/utils.js:
--------------------------------------------------------------------------------
1 | const
2 | fs = require('fs'),
3 | path = require('path'),
4 | zlib = require('zlib'),
5 | { green, blue, red, cyan } = require('chalk'),
6 | kebabRegex = /[A-Z\u00C0-\u00D6\u00D8-\u00DE]/g
7 |
8 | function getSize (code) {
9 | return (code.length / 1024).toFixed(2) + 'kb'
10 | }
11 |
12 | module.exports.createFolder = function (folder) {
13 | const dir = path.join(__dirname, '..', folder)
14 | if (!fs.existsSync(dir)) {
15 | fs.mkdirSync(dir)
16 | }
17 | }
18 |
19 | module.exports.writeFile = function (dest, code, zip) {
20 | const banner = dest.indexOf('.json') > -1
21 | ? red('[json]')
22 | : dest.indexOf('.js') > -1
23 | ? green('[js] ')
24 | : dest.indexOf('.ts') > -1
25 | ? cyan('[ts] ')
26 | : blue('[css] ')
27 |
28 | return new Promise((resolve, reject) => {
29 | function report (extra) {
30 | console.log(`${banner} ${path.relative(process.cwd(), dest).padEnd(41)} ${getSize(code).padStart(8)}${extra || ''}`)
31 | resolve(code)
32 | }
33 |
34 | fs.writeFile(dest, code, err => {
35 | if (err) return reject(err)
36 | if (zip) {
37 | zlib.gzip(code, (err, zipped) => {
38 | if (err) return reject(err)
39 | report(` (gzipped: ${getSize(zipped).padStart(8)})`)
40 | })
41 | }
42 | else {
43 | report()
44 | }
45 | })
46 | })
47 | }
48 |
49 | module.exports.readFile = function (file) {
50 | return fs.readFileSync(file, 'utf-8')
51 | }
52 |
53 | module.exports.logError = function (err) {
54 | console.error('\n' + red('[Error]'), err)
55 | console.log()
56 | }
57 |
--------------------------------------------------------------------------------
/ui/dev/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | indent_style = space
6 | indent_size = 2
7 | end_of_line = lf
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
--------------------------------------------------------------------------------
/ui/dev/.firebase/hosting.ZGlzdC9zcGE.cache:
--------------------------------------------------------------------------------
1 | css/7.0e433876.css,1590723944563,f1b651238a58fe290baec6c5e32f3bdb1943dd2bd582f02569231f7a757c7837
2 | index.html,1590723944572,ffd050dd3badbbe455ad420f803ed5382effb2d121497bcbf0caafdef19bc2b8
3 | css/2.e669d2e7.css,1590723944561,77044317b9fdae84324079dcac7871562a8aa7c3a2f99fa99c1dacd49f6a87ef
4 | css/6.38879e49.css,1590723944564,257eb8d7c8d659d1d4673d81c77d148bca4e9b16ef601f733c54dee0abeedb38
5 | css/8.21c4a94b.css,1590723944564,a9c14f6ec4080423405590ea65fde098242b961bd4a84710d133689b60f7dbb4
6 | css/chunk-common.01efd12b.css,1590723944559,3571323bd7aac4d1cce6318bb4f0ff22e12e88ede3b3ac79f093f3c228cb3c9e
7 | js/10.9143b4f6.js,1590723944565,c235376741fc18a371f9a6d68a30fa2b03c73ba5088838b1672bf6a167c6f011
8 | js/12.49befc1b.js,1590723944565,ee2021e3df6a1dcd147e013266e60fa68e22654b414bdc22fb0825cc99584ae6
9 | js/11.ba239275.js,1590723944565,14abd0e1dec2e22bfadc5f3a799010dfb91f7915f7f829d2397d3397c9ec1bab
10 | js/13.6aa78827.js,1590723944565,e7bc013a6f8acb0a4de3fe9d7a3e4eb10d896d20f10f9659b035806742574dbf
11 | js/14.f1e6e009.js,1590723944565,7757d5991a59b32f0cb6b6a51b84a63b8215b01e559c886112a36288d17d6d57
12 | js/15.74c59a59.js,1590723944565,cf12887d3d2fd755769f13b024a9a27d3b0bdf48fa6ba0c2f97dd5a167d31a2d
13 | js/16.1df5fbd5.js,1590723944566,ec7ab42a126240ea7c3bbe34d3070682b28226aa6a2fa86f4c40865a7a925027
14 | js/17.f36d6ce8.js,1590723944566,69a38b085a87e8cb07c518ff7792eddd8df56f389effe3d639e143d19a7e9118
15 | js/18.8e6448a5.js,1590723944566,6127a20507b47dca4be7ca4ea8693b02fd672b80946398b93d791f5580ae3c54
16 | js/19.eccff282.js,1590723944567,a0cce2d545d28b78eee797967bbfc84916a738dc11412103b0a00ed734c8a571
17 | js/20.ddb21d19.js,1590723944567,24946d2a8f10ea722271ceb0c94b61bd1611fe0ce3384bb619d38052a6af7545
18 | js/21.922a1af6.js,1590723944567,8a40beca8fdfe9942a71f733951dd82689a9d280758aed3fea17fd69c9133791
19 | js/22.da4317a8.js,1590723944567,e9c2305b3c0d55779991dfb2c9f44c7649a27cdfb32dcdcbb532301cebb37f35
20 | js/23.f24abdac.js,1590723944567,80cccd825c80cd4d61e9b6c6615ebd00e2bc8cb52509224aeb627241b912c745
21 | js/24.25657f20.js,1590723944567,c1cd275bdc262e5ea312c68e3a2faf1c62a7da3ad01855cd4f4ffd6d8aaef3a3
22 | js/25.8487808e.js,1590723944567,9779cb6651f9ea6662dc909b123b7cae139c178123c67fbcbe3cc4442b20267a
23 | js/2.94549b58.js,1590723944561,0c4026b76420a55630d5491207be0a0ef369f41d78aa1e030f5b428e1bb4766c
24 | js/26.f349d5b2.js,1590723944567,acc385ad992045e95d3e4eb4396c199bcfd4c6cbfebb21dd2dbdc8410ad90f41
25 | js/28.299cb9ee.js,1590723944568,4096769daeee6ceceecd52de087ea21a4f437f92bc1c162b0078916cbf944276
26 | js/27.9da6f3bd.js,1590723944568,3a3f3e57400c8b91aeabb267613faaa498dc587d9b7b9ac41c9846f79259eb28
27 | js/29.20ec51bf.js,1590723944568,7c86bbaad920d4f4ed5585a0a663f6789f7a27cf5f7f89fe5db06ebeed9be228
28 | js/3.72dfc66c.js,1590723944561,c333d3153d2831058b2eb5b49c88f6264c094558119ed43cb0e16021e925d9e5
29 | js/30.89164d26.js,1590723944568,d36cc8ef1157c3c3c819b5c7c666385e0abe2b09f48e5d2f129c695e155da80c
30 | js/31.524fc0da.js,1590723944569,e270ea7e0aa257edf9c6ee321f44590947afb25c5c7c516ac5459c7ac63a04b3
31 | js/33.b9ba0943.js,1590723944570,dd27312495a68c2c9b84f5744fde250401ec1b1a33246691c0042c12f48ca30b
32 | js/6.30de475d.js,1590723944564,9fd7563cbfd64397d66bed747fbf9216c8b6fef8ae2c7d8ba72d9b4f5ab85004
33 | js/32.2ec5c654.js,1590723944569,69c2a5867e404bc644e712de13ff9fcb2b519647f872db3872ef1327479f4e76
34 | js/7.5a6efbb0.js,1590723944564,49d39fd5004eac431744c87f3bf1b399677bf54b23bc27b84c584392cea5f4d2
35 | js/9.ac3a7338.js,1590723944565,651e3fbd0b6f4e3ff3a64861b150543a8b9c6a360ac5e22606b3a74664e04dc1
36 | js/8.c3204439.js,1590723944564,4ccc4118b746df9abc8a994bc30ced7584a0d540307430ecf7e985ec770b2d28
37 | js/runtime.88fc62d4.js,1590723944564,2299ad1e1963d648e8bd69e412729cd9fb5f537e06b4b852367cab3aecefd837
38 | statics/app-logo-128x128.png,1590723944569,3ba36b6fe48820fa565c588c7057d5b20de3777c1ac01299e7a9f090a558f175
39 | fonts/KFOlCnqEu92Fr1MmEU9fBBc-.cea99d3e.woff,1590723944562,42c7afdcff7aea098db26a417a9bb7929eed6df8823461b05cea5e6185216248
40 | fonts/KFOlCnqEu92Fr1MmSU5fBBc-.865f928c.woff,1590723944558,92689fd58bd8ba23da284d871afeb0e05fce981a9a28464b0acbad2051f6545a
41 | fonts/KFOkCnqEu92Fr1MmgVxIIzQ.a45108d3.woff,1590723944561,0cc82ea4e2383c3b0d0f46b32471cee5e920f87d2a151d302635cb2ca8cb6e43
42 | fonts/KFOlCnqEu92Fr1MmYUtfBBc-.bac8362e.woff,1590723944561,f225f153b48f4208f17a7fcc4595d2832b5b29916c357c8f7979ba64831b9ad0
43 | fonts/KFOmCnqEu92Fr1Mu4mxM.49ae34d4.woff,1590723944561,68fb4aa339f56ddb03b15f12cae4f82cd06640dca3dcfe230b802593f36312e4
44 | js/app.d9abd9e4.js,1590723944563,48dbade60e51fec2562dddcb9ff53dc06ccd1be9c758af528642b97cfb50fda6
45 | fonts/KFOlCnqEu92Fr1MmWUlfBBc-.2267169e.woff,1590723944561,de91d90d88d21edee1be62c920126d606e0beac61e6ee2243c417490b107279e
46 | statics/icons/apple-icon-120x120.png,1590723944572,9e147671e0051a9fe6042d4a0797037f077100fcb121d4206f4543838ddf87eb
47 | js/chunk-common.850d6742.js,1590723944561,bff60b1114b70951d86d8f13ff0fc8dcc4da0d63b00c41f3da3b1689c57e3c6d
48 | fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.12a47ed5.woff2,1590723944561,a99652277b6479b04b3231319123ca269fe04f85cddac90b8dcbc142d4625d4d
49 | statics/icons/apple-icon-152x152.png,1590723944572,6dc20f0fb7da357654cb1a9312a74702e1e97ceb3056d67b5ad0a066e96c0ed8
50 | statics/icons/apple-icon-167x167.png,1590723944570,89d93abc376213db7d1663adc8bd595d76daccd4e2890e29f19152e33d2ee4d8
51 | statics/icons/apple-icon-180x180.png,1590723944570,43f646dadecb2dbabbe53a3e888aa3c377594f843167dd54001fa3a34a4cabaa
52 | statics/icons/favicon-16x16.png,1590723944571,9a9519211345ba591ae9ecd63082d20cdec901bd22da4ff5fa1c0c0aa45fdbee
53 | statics/icons/favicon-32x32.png,1590723944571,6b423caf88b215fc7f52e76642141dd78522862cc989114233eb50c77deb16d8
54 | statics/icons/favicon-96x96.png,1590723944570,aea2f51632b5e5a4d9bbb3fd010b438940bb568838f6cb186a4b9e33eb85e562
55 | statics/icons/favicon.ico,1590723944571,f4066f8f9d3212c5156547c3e2e2f607d1b17f1887f96d3a0bf59221e6eb519e
56 | statics/icons/icon-128x128.png,1590723944571,3ba36b6fe48820fa565c588c7057d5b20de3777c1ac01299e7a9f090a558f175
57 | statics/icons/icon-192x192.png,1590723944571,efe6efc75397aab84684918e7c45e24aadc2f8612789d6c91a6e7432d47724c2
58 | statics/icons/ms-icon-144x144.png,1590723944573,a1bee2aec520eea50583e3ace8461eb229b8bd5679c573cabd6c54a37b2286da
59 | statics/icons/safari-pinned-tab.svg,1590723944573,3356180d03fd5493efc6f27816d1d4e5f908ac300f5efb9f8044d9ef8823c4d1
60 | statics/icons/icon-256x256.png,1590723944571,fef8007201e545ef561f5fa50a12fa654c0fa9f03e9d998b6199727de8238c25
61 | fonts/flUhRq6tzZclQEJ-Vdg-IuiaDsNa.f2a09334.woff,1590723944561,64fc5686c655301234b304dfb176d6ca1d6fd56f9434abf380b3549ce0058b69
62 | css/app.82899d9d.css,1590723944562,4d21eaef64e9082fc91f8654c1373fa98da4e66866c5a2d0c6f0c91225b7472e
63 | statics/icons/icon-384x384.png,1590723944573,4e90fdacc7e781857ab93ece4b8e816b65ed69319f3abbe18367df2f47111049
64 | statics/icons/icon-512x512.png,1590723944573,83cb2337e81081fd5a948805b4fb550c68daa81346add2e8ea86f43d13f6ce9d
65 | js/vendor.07ad97b1.js,1590723944559,18a3167319fa88282a8b4870eb00fca71a03a5e5c777b685769692d471ece097
66 |
--------------------------------------------------------------------------------
/ui/dev/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "quasar-easy-forms"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/ui/dev/.gitignore:
--------------------------------------------------------------------------------
1 | .quasar
2 | .DS_Store
3 | .thumbs.db
4 | node_modules
5 | /dist
6 | /src-cordova/node_modules
7 | /src-cordova/platforms
8 | /src-cordova/plugins
9 | /src-cordova/www
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 |
--------------------------------------------------------------------------------
/ui/dev/.postcssrc.js:
--------------------------------------------------------------------------------
1 | // https://github.com/michael-ciniawsky/postcss-load-config
2 |
3 | module.exports = {
4 | plugins: [
5 | // to edit target browsers: use "browserslist" field in package.json
6 | require('autoprefixer')
7 | ]
8 | }
9 |
--------------------------------------------------------------------------------
/ui/dev/README.md:
--------------------------------------------------------------------------------
1 | # Dev app (playground)
2 |
3 | Adding .vue files to src/pages/ will auto-add them to the Index page list.
4 |
--------------------------------------------------------------------------------
/ui/dev/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@quasar/babel-preset-app'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/ui/dev/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "hosting": {
3 | "public": "dist/spa",
4 | "ignore": [
5 | "firebase.json",
6 | "**/.*",
7 | "**/node_modules/**"
8 | ],
9 | "rewrites": [
10 | {
11 | "source": "**",
12 | "destination": "/index.html"
13 | }
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ui/dev/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dev",
3 | "version": "1.0.0",
4 | "description": "A Quasar Framework app",
5 | "productName": "Quasar App",
6 | "cordovaId": "org.cordova.quasar.app",
7 | "private": true,
8 | "scripts": {
9 | "build": "quasar build",
10 | "deploy": "quasar build && firebase deploy",
11 | "dev": "quasar dev",
12 | "dev:ssr": "quasar dev -m ssr",
13 | "dev:ios": "quasar dev -m ios",
14 | "dev:android": "quasar dev -m android",
15 | "dev:electron": "quasar dev -m electron"
16 | },
17 | "dependencies": {
18 | "@planetar/api-card": "^0.1.8",
19 | "@planetar/example-card": "^0.1.2",
20 | "@quasar/extras": "^1.8.1",
21 | "case-anything": "^0.3.1",
22 | "copy-anything": "^1.6.0",
23 | "is-what": "^3.8.0",
24 | "merge-anything": "^2.4.4",
25 | "quasar-ui-component-info-card": "0.0.7",
26 | "raw-loader": "^4.0.1",
27 | "snarkdown": "github:mesqueeb/snarkdown#dist",
28 | "sort-anything": "0.0.1"
29 | },
30 | "devDependencies": {
31 | "@quasar/app": "^1.8.10"
32 | },
33 | "engines": {
34 | "node": ">= 8.9.0",
35 | "npm": ">= 5.6.0",
36 | "yarn": ">= 1.6.0"
37 | },
38 | "browserslist": [
39 | "last 1 version, not dead, ie >= 11"
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/ui/dev/quasar.conf.js:
--------------------------------------------------------------------------------
1 | // Configuration for your app
2 | // https://quasar.dev/quasar-cli/quasar-conf-js
3 |
4 | const path = require('path')
5 |
6 | module.exports = function (ctx) {
7 | return {
8 | // app boot file (/src/boot)
9 | // --> boot files are part of "main.js"
10 | boot: ['vueComponents.js', 'register.js'],
11 |
12 | css: ['app.sass'],
13 |
14 | extras: [
15 | // 'ionicons-v4',
16 | // 'mdi-v4',
17 | // 'fontawesome-v5',
18 | // 'eva-icons',
19 | // 'themify',
20 | // 'roboto-font-latin-ext', // this or either 'roboto-font', NEVER both!
21 |
22 | 'roboto-font', // optional, you are not bound to it
23 | 'material-icons', // optional, you are not bound to it
24 | ],
25 |
26 | framework: {
27 | // iconSet: 'ionicons-v4', // Quasar icon set
28 | // lang: 'de', // Quasar language pack
29 |
30 | // Possible values for "all":
31 | // * 'auto' - Auto-import needed Quasar components & directives
32 | // (slightly higher compile time; next to minimum bundle size; most convenient)
33 | // * false - Manually specify what to import
34 | // (fastest compile time; minimum bundle size; most tedious)
35 | // * true - Import everything from Quasar
36 | // (not treeshaking Quasar; biggest bundle size; convenient)
37 | all: 'auto',
38 |
39 | components: [
40 | 'QBtn',
41 | 'QIcon',
42 | 'QPopupProxy',
43 | 'QColor',
44 | 'QInput',
45 | 'QBtnToggle',
46 | 'QSlider',
47 | 'QSelect',
48 | 'QToggle',
49 | 'QOptionGroup',
50 | ],
51 | directives: ['ClosePopup'],
52 |
53 | // Quasar plugins
54 | plugins: ['Dialog', 'Notify'],
55 | },
56 |
57 | supportIE: false,
58 |
59 | // animations: 'all', // --- includes all animations
60 | animations: [],
61 |
62 | build: {
63 | vueRouterMode: 'history',
64 |
65 | chainWebpack (chain) {
66 | chain.resolve.alias.merge({
67 | ui: path.resolve(__dirname, '../src/index.js'),
68 | })
69 | chain.module
70 | .rule('md')
71 | .test(/\.md$/i)
72 | .use('raw-loader')
73 | .loader('raw-loader')
74 | },
75 | },
76 |
77 | devServer: {
78 | // port: 8080,
79 | open: 'firefox', // opens browser window automatically
80 | },
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/ui/dev/quasar.extensions.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/ui/dev/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/ui/dev/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/assets/.gitkeep
--------------------------------------------------------------------------------
/ui/dev/src/boot/register.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VuePlugin from 'ui' // "ui" is aliased in quasar.conf.js
3 |
4 | Vue.use(VuePlugin)
5 |
--------------------------------------------------------------------------------
/ui/dev/src/boot/vueComponents.js:
--------------------------------------------------------------------------------
1 | import { InfoCard } from 'quasar-ui-component-info-card'
2 | import InfoBoxWrapper from '../components/InfoBoxWrapper'
3 | import PrimaryColorPicker from '../components/PrimaryColorPicker'
4 | import Snarkdown from '../components/Snarkdown'
5 |
6 | export default ({ Vue }) => {
7 | Vue.component('InfoCard', InfoCard)
8 | Vue.component(InfoBoxWrapper.name, InfoBoxWrapper)
9 | Vue.component(PrimaryColorPicker.name, PrimaryColorPicker)
10 | Vue.component(Snarkdown.name, Snarkdown)
11 | }
12 |
--------------------------------------------------------------------------------
/ui/dev/src/components/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/components/.gitkeep
--------------------------------------------------------------------------------
/ui/dev/src/components/InfoBoxWrapper.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ label }}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
30 |
31 |
53 |
--------------------------------------------------------------------------------
/ui/dev/src/components/PrimaryColorPicker.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Update color:
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
22 |
23 |
42 |
--------------------------------------------------------------------------------
/ui/dev/src/components/Snarkdown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
27 |
--------------------------------------------------------------------------------
/ui/dev/src/components/Template.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
20 |
--------------------------------------------------------------------------------
/ui/dev/src/components/showMoreWrapper.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
28 | {{ showingMore ? showLess : showMore }}
29 |
30 |
31 |
32 |
33 |
68 |
69 |
99 |
--------------------------------------------------------------------------------
/ui/dev/src/css/app.sass:
--------------------------------------------------------------------------------
1 | @import 'colors'
2 | @import 'typography'
3 |
4 | // app global css in Sass form
5 | .text-wrap-all
6 | white-space: pre-line
7 | word-break: break-word
8 |
--------------------------------------------------------------------------------
/ui/dev/src/css/colors.sass:
--------------------------------------------------------------------------------
1 |
2 | // todo, delete this
3 | $c-mercury: #EAE9E9
4 |
5 | /**
6 | * There is no pure black in this interface, instead there is the awesomeness that is Blue-Zodiac.
7 | * Use blue-zodiac instead of black, and use it’s opacity variants for text and icon colors.
8 | * /
9 | $c-blue-zodiac: #0E2348
10 | $c-primary: rgba($c-blue-zodiac, 0.96)
11 | $c-secondary: rgba($c-blue-zodiac, 0.72)
12 | $c-hint: rgba($c-blue-zodiac, 0.56)
13 | $c-disabled: rgba($c-blue-zodiac, 0.28)
14 |
15 | /**
16 | * Dandelion is always used to signify what is of primary importance to the user. It can be applied to any action that is considered primary, and there can be up to two elements of primary importance per view. (Never more than two.)
17 | */
18 | $c-dandelion: #FFD465
19 | $c-dandelion-light: #FFE9B1
20 | $c-dandelion-dark: #E4A500
21 |
22 | /**
23 | * Breezy and beautiful, Sail is used to signify what is of secondary importance to the user.
24 | */
25 | $c-sail: #C1D9FE
26 | $c-sail-dark: #76ABFD
27 |
28 | /**
29 | * Using Blue-Ribbon is powerful. It signifies to the user that they can take action on an item, whether that item is an icon or a text link.
30 | * Listed along-side Blue-Ribbon are the colors Azure-Skies and Endeavor. These two colors work along-side Blue-Ribbon in the interface, but are named with unique names due to their presence in the logo mark.
31 | */
32 | $c-blue-ribbon: #0563ED
33 | $c-azure-skies: #2989FF
34 | $c-endeavour: #0152A4
35 |
36 | /**
37 | * The subtlety of Stone allows it to serve as background color, and as a tool to separate elements on the page. Use Stone for backgrounds, less important elements, and divider lines.
38 | */
39 | $c-stone: #EFF3F9
40 | $c-stone-light: #F6F9FC
41 | $c-stone-dark: #D9E2F1
42 |
43 | /**
44 | * Use the Error color to signify to the user that they need to take a corrective action.
45 | */
46 | $c-error: #E52703
47 |
48 | /**
49 | * Success is used to let the user know that an input was successful.
50 | */
51 | $c-success: #07634D
52 |
53 | /**
54 | * Dark mode colours
55 | */
56 | $c-lucy-black: #1a1d26
57 |
58 | $colors: ("mercury": $c-mercury, "blue-zodiac": $c-blue-zodiac, "primary": $c-primary, "secondary": $c-secondary, "hint": $c-hint, "disabled": $c-disabled, "dandelion": $c-dandelion, "dandelion-light": $c-dandelion-light, "dandelion-dark": $c-dandelion-dark, "sail": $c-sail, "sail-dark": $c-sail-dark, "blue-ribbon": $c-blue-ribbon, "azure-skies": $c-azure-skies, "endeavour": $c-endeavour, "stone": $c-stone, "stone-light": $c-stone-light, "stone-dark": $c-stone-dark, "error": $c-error, "success": $c-success)
59 |
60 | @each $name, $color in $colors
61 | .bg-#{$name}
62 | background: $color
63 | .c-#{$name}
64 | color: $color
65 | .text-#{$name}
66 | color: $color
67 |
--------------------------------------------------------------------------------
/ui/dev/src/css/quasar.variables.sass:
--------------------------------------------------------------------------------
1 | // Quasar Sass (& SCSS) Variables
2 | // --------------------------------------------------
3 | // To customize the look and feel of this app, you can override
4 | // the Stylus variables found in Quasar's source Stylus files.
5 |
6 | // Check documentation for full list of Quasar variables
7 |
8 | // Your own variables (that are declared here) and Quasar's own
9 | // ones will be available out of the box in your .vue/.scss/.sass files
10 |
11 | // It's highly recommended to change the default colors
12 | // to match your app's branding.
13 | // Tip: Use the "Theme Builder" on Quasar's documentation website.
14 |
15 | $primary : #9c81dc
16 | $secondary : #26A69A
17 | $accent : #9C27B0
18 |
19 | $positive : #21BA45
20 | $negative : #C10015
21 | $info : #31CCEC
22 | $warning : #F2C037
23 |
24 | $_space-base: 16px
25 |
26 | $xxs: ($_space-base * .1)
27 | $xs: ($_space-base * .25)
28 | $sm: ($_space-base * .5)
29 | $md: $_space-base
30 | $lg: ($_space-base * 1.5)
31 | $xl: ($_space-base * 2.3)
32 | $xxl: ($_space-base * 3)
33 | $xxxl: ($_space-base * 5)
34 |
--------------------------------------------------------------------------------
/ui/dev/src/css/typography.sass:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Open+Sans&family=Poppins:wght@400;500;600;700&display=swap")
2 | @import 'colors'
3 |
4 | =font-globals()
5 | font-family: 'Poppins', sans-serif
6 | color: $c-primary
7 |
8 | =t-h1()
9 | +font-globals
10 | font-weight: bold
11 | font-size: 56px
12 | line-height: 64px
13 | letter-spacing: 0.01em
14 |
15 | =t-h2()
16 | +font-globals
17 | font-weight: 600
18 | font-size: 40px
19 | line-height: 56px
20 |
21 | =t-h3()
22 | +font-globals
23 | font-weight: bold
24 | font-size: 36px
25 | line-height: 40px
26 | letter-spacing: 0.01em
27 |
28 | =t-h4()
29 | +font-globals
30 | font-weight: 600
31 | font-size: 32px
32 | line-height: 40px
33 |
34 | =t-h5()
35 | +font-globals
36 | font-weight: 600
37 | font-size: 24px
38 | line-height: 30px
39 | letter-spacing: 0.01em
40 |
41 | =t-h6()
42 | +font-globals
43 | font-weight: 600
44 | font-size: 20px
45 | line-height: 24px
46 |
47 | =t-subtitle1()
48 | +font-globals
49 | font-weight: 600
50 | font-size: 16px
51 | line-height: 20px
52 |
53 | =t-subtitle2()
54 | +font-globals
55 | font-weight: 500
56 | font-size: 14px
57 | line-height: 20px
58 | letter-spacing: 0.005em
59 |
60 | =t-body1()
61 | +font-globals
62 | font-family: 'Open Sans', sans-serif
63 | font-weight: normal
64 | font-size: 16px
65 | line-height: 24px
66 |
67 | =t-body2()
68 | +font-globals
69 | font-family: 'Open Sans', sans-serif
70 | font-weight: normal
71 | font-size: 14px
72 | line-height: 20px
73 | letter-spacing: 0.01em
74 |
75 | =t-button()
76 | +font-globals
77 | font-weight: 600
78 | font-size: 14px
79 | line-height: 24px
80 | letter-spacing: 0.01em
81 |
82 | =t-caption()
83 | +font-globals
84 | font-weight: normal
85 | font-size: 12px
86 | line-height: 16px
87 | letter-spacing: 0.02em
88 |
89 | =t-caption-semibold()
90 | +font-globals
91 | font-weight: 600
92 | font-size: 12px
93 | line-height: 16px
94 | letter-spacing: 0.02em
95 |
96 | =t-overline()
97 | +font-globals
98 | font-weight: 600
99 | font-size: 12px
100 | line-height: 16px
101 | letter-spacing: 0.08em
102 | text-transform: uppercase
103 |
104 | =t-micro()
105 | +font-globals
106 | font-weight: normal
107 | font-size: 10px
108 | line-height: 14px
109 | letter-spacing: 0.02em
110 |
111 | .t-h1, h1
112 | +t-h1
113 | .t-h2, h2
114 | +t-h2
115 | .t-h3, h3
116 | +t-h3
117 | .t-h4, h4
118 | +t-h4
119 | .t-h5, h5
120 | +t-h5
121 | .t-h6, h6
122 | +t-h6
123 | .t-subtitle1
124 | +t-subtitle1
125 | .t-subtitle2
126 | +t-subtitle2
127 | .t-body1
128 | +t-body1
129 | .t-body2
130 | +t-body2
131 | .t-button
132 | +t-button
133 | .t-caption
134 | +t-caption
135 | .t-caption-semibold
136 | +t-caption-semibold
137 | .t-overline
138 | +t-overline
139 | .t-micro
140 | +t-micro
141 |
142 | h1, h2, h3, h4, h5, h6
143 | margin-top: 17px
144 | margin-bottom: 17px
145 |
--------------------------------------------------------------------------------
/ui/dev/src/helpers/conversion.js:
--------------------------------------------------------------------------------
1 | import { isFullString } from 'is-what'
2 |
3 | export function stringToJs (string) {
4 | if (!isFullString(string)) return undefined
5 | const jsonString = string.replace(/([a-zA-Z0-9]+?):/g, '"$1":').replace(/'/g, '"')
6 | let parsed
7 | try {
8 | parsed = JSON.parse(jsonString)
9 | } catch (e) {
10 | return string
11 | }
12 | return parsed
13 | }
14 |
--------------------------------------------------------------------------------
/ui/dev/src/helpers/pageUtils.js:
--------------------------------------------------------------------------------
1 | import { camelCase } from 'case-anything'
2 |
3 | export function copyToClipboard (text) {
4 | const camelText = camelCase(text)
5 | var textArea = document.createElement('textarea')
6 | textArea.className = 'fixed-top'
7 | textArea.value = camelText
8 | document.body.appendChild(textArea)
9 | textArea.focus()
10 | textArea.select()
11 |
12 | document.execCommand('copy')
13 | document.body.removeChild(textArea)
14 | }
15 |
16 | export function copyHeading (id) {
17 | const text = window.location.origin + window.location.pathname + '#' + id
18 |
19 | copyToClipboard(text)
20 |
21 | this.$q.notify({
22 | message: 'Anchor has been copied to clipboard.',
23 | color: 'white',
24 | textColor: 'primary',
25 | position: 'top',
26 | actions: [{ icon: 'close', color: 'primary' }],
27 | timeout: 2000,
28 | })
29 | }
30 |
--------------------------------------------------------------------------------
/ui/dev/src/helpers/parseCodeForPrint.js:
--------------------------------------------------------------------------------
1 | import { isFunction } from 'is-what'
2 |
3 | export default function parseCodeForPrint (code) {
4 | // return early on 0, undefined, null, etc.
5 | if (!code) return code
6 | const stringifiedFns = []
7 | function replacer (key, value) {
8 | if (isFunction(value) && value.prototype.stringifiedFn) {
9 | const fnString = value.prototype.stringifiedFn
10 | stringifiedFns.push(fnString)
11 | return fnString
12 | }
13 | return value
14 | }
15 | const string = JSON.stringify(code, replacer, 2)
16 | const cleanedString = string.replace(/'/g, `\\'`).replace(/"/g, `'`)
17 | const parsedString = stringifiedFns.reduce((str, fnString) => {
18 | const fnStringRegex = `'${fnString.replace(/'/g, `\\'`).replace(/"/g, `'`)}'`
19 | return str.replace(fnStringRegex, fnString)
20 | }, cleanedString)
21 | return parsedString
22 | }
23 |
--------------------------------------------------------------------------------
/ui/dev/src/helpers/schemaBuilders.js:
--------------------------------------------------------------------------------
1 | import { isArray, isUndefined, isFunction } from 'is-what'
2 | import copy from 'copy-anything'
3 | import merge from 'merge-anything'
4 | import EasyForms from 'ui'
5 | import { pascalCase } from 'case-anything'
6 | import { stringToJs } from './conversion'
7 | const { dependencyMap } = EasyForms
8 | const { getPassedProps } = dependencyMap
9 |
10 | export function getRawComponent (selectedField) {
11 | return EasyForms[selectedField] || {}
12 | }
13 |
14 | export function getAllComponentProps (selectedField) {
15 | const component = getRawComponent(selectedField) || {}
16 | const componentProps = component.props || {}
17 | const EasyField = EasyForms['EasyField'] || {}
18 | const EasyFieldProps = EasyField.props || {}
19 | const inheritedComponentProps = getPassedProps(selectedField) || {}
20 | const result = copy(merge(inheritedComponentProps, EasyFieldProps, componentProps))
21 | return result
22 | }
23 |
24 | export function propToPropSchema (propKey, propInfo) {
25 | const { desc, type, inheritedProp, examples, default: df, values, category } = propInfo
26 | // make the raw prop info from the components into an EasyForm:
27 | // whatever the prop is, default to an 'input' EasyField
28 | const events = {}
29 | let component = 'QInput'
30 | let subLabel = desc
31 | let options, standout, disable, parseInput, parseValue, autogrow, debounce, span, emitValue, _type
32 | let outlined = true
33 | let fieldClasses = []
34 | let _default = df === true || undefined
35 | // If it has a default, write it in the description
36 | if (!isUndefined(df)) subLabel += `\n\nDefault: \`${isFunction(df) ? JSON.stringify(df()) : df}\``
37 | // if the prop is a Boolean, show this as a 'toggle' EasyField
38 | if (type === Boolean || (isArray(type) && type.includes(Boolean))) {
39 | component = 'QToggle'
40 | _default = df === true
41 | }
42 | // if it's a Number field
43 | if (type === Number) {
44 | _type = 'number'
45 | parseInput = Number
46 | }
47 | // if the prop has a fixed set of possible values, show this as an 'option' EasyField
48 | const propHasValues = isArray(values) && values.length
49 | if (propHasValues) {
50 | component = 'QSelect'
51 | emitValue = true
52 | options = values.map(v => ({ label: v, value: v }))
53 | }
54 | // Create a special input for defining arrays and/or objects
55 | if (
56 | type === Array ||
57 | type === Object ||
58 | type === Function ||
59 | (isArray(type) && [Array, Object].some(t => type.includes(t)) && type.length === 2)
60 | ) {
61 | // events.blur = (e, val) => console.log(stringToJs(val))
62 | outlined = false
63 | standout = true
64 | debounce = 500
65 | parseInput = stringToJs
66 | parseValue = JSON.stringify
67 | autogrow = true
68 | if (isArray(examples)) subLabel += `\nExamples: \`${examples.join('` | `')}\``
69 | }
70 | // Don't allow editing props that accept functions.
71 | if (type === Function) disable = true
72 | // If it's the prop called 'schema', span the entire form, add extra info and don't return any input field
73 | if (propKey === 'schema') {
74 | component = ''
75 | span = true
76 | subLabel +=
77 | '\n\n> 👀 Check「Source tab」→「Schema」to see the following code in color and with indentation.'
78 | }
79 | // Create the EasyField schema for the prop
80 | return {
81 | id: propKey,
82 | component,
83 | type: _type,
84 | // schema,
85 | label: propKey,
86 | subLabel,
87 | placeholder: !isArray(examples) ? '' : examples.join(', '),
88 | inheritedProp,
89 | options,
90 | outlined,
91 | standout,
92 | disable,
93 | parseInput,
94 | parseValue,
95 | autogrow,
96 | category,
97 | fieldClasses,
98 | debounce,
99 | events,
100 | span,
101 | emitValue,
102 | // if the prop is `true` by default, set to true
103 | default: _default,
104 | // defaults
105 | hasMarkdown: true,
106 | }
107 | }
108 |
109 | export function getInfoCardPropsSchema (selectedField) {
110 | const allComponentProps =
111 | selectedField === 'EasyForm'
112 | ? copy(EasyForms['EasyForm'].props)
113 | : getAllComponentProps(selectedField)
114 | return Object.entries(allComponentProps).reduce((carry, [propKey, propInfo]) => {
115 | // fields to not include in the InfoCard settings:
116 | if (propKey === 'component' || propKey === 'value') {
117 | return carry
118 | }
119 | carry[propKey] = propToPropSchema(propKey, propInfo)
120 | return carry
121 | }, {})
122 | }
123 |
--------------------------------------------------------------------------------
/ui/dev/src/index.template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | <%= htmlWebpackPlugin.options.productName %>
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/ui/dev/src/layouts/MyLayout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | quasar-ui-easy-forms
9 |
10 |
11 | demo versions:
12 |
13 |
v{{ version }}
14 |
quasar-ui-easy-forms
15 |
16 |
17 |
v{{ $q.version }}
18 |
quasar
19 |
20 |
21 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
47 |
--------------------------------------------------------------------------------
/ui/dev/src/new-examples/Basics.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
54 |
--------------------------------------------------------------------------------
/ui/dev/src/pages/EasyFieldDemo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
18 |
24 |
25 |
26 |
27 |
28 |
45 |
46 |
129 |
--------------------------------------------------------------------------------
/ui/dev/src/pages/EasyFormDemo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
19 |
25 |
26 |
27 |
28 |
29 |
46 |
47 |
126 |
--------------------------------------------------------------------------------
/ui/dev/src/pages/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | < EasyForm />
5 |
6 | Generate an entire form via just a "schema" object. Use any component you want!
7 |
8 |
9 |
10 |
11 |
12 |
13 | {{ page.title }}
14 |
15 |
16 |
17 |
18 |
19 | < EasyField />
20 | Bonus! Some handy fields usable inside an EasyForm. 😃
21 |
22 |
23 |
24 |
25 |
26 | {{ page.title }}
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | Essential Links
35 |
36 |
42 |
43 |
58 |
59 |
60 | Github
61 | mesqueeb/quasar-ui-easy-forms
62 |
63 |
64 |
65 |
66 |
67 |
72 |
73 |
74 | Twitter
75 | @mesqueeb
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | Quasar Framework
85 | quasar.dev
86 |
87 |
88 |
89 |
90 |
91 |
92 |
123 |
124 |
130 |
--------------------------------------------------------------------------------
/ui/dev/src/pages/NewDemo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
30 |
--------------------------------------------------------------------------------
/ui/dev/src/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import VueRouter from 'vue-router'
3 |
4 | import routes from './routes'
5 |
6 | Vue.use(VueRouter)
7 |
8 | /*
9 | * If not building with SSR mode, you can
10 | * directly export the Router instantiation
11 | */
12 |
13 | export default function (/* { store, ssrContext } */) {
14 | const Router = new VueRouter({
15 | scrollBehavior: () => ({ x: 0, y: 0 }),
16 | routes,
17 |
18 | // Leave these as is and change from quasar.conf.js instead!
19 | // quasar.conf.js -> build -> vueRouterMode
20 | // quasar.conf.js -> build -> publicPath
21 | mode: process.env.VUE_ROUTER_MODE,
22 | base: process.env.VUE_ROUTER_BASE,
23 | })
24 |
25 | // we get each page from server first!
26 | if (process.env.MODE === 'ssr' && process.env.CLIENT) {
27 | console.log('!!!!')
28 | console.log(
29 | 'On route change we deliberately load page from server -- in order to test hydration errors'
30 | )
31 | console.log('!!!!')
32 |
33 | let reload = false
34 | Router.beforeEach((to, _, next) => {
35 | if (reload) {
36 | window.location.href = to.fullPath
37 | return
38 | }
39 | reload = true
40 | next()
41 | })
42 | }
43 |
44 | return Router
45 | }
46 |
--------------------------------------------------------------------------------
/ui/dev/src/router/routes.js:
--------------------------------------------------------------------------------
1 | const routes = [
2 | {
3 | path: '/',
4 | component: () => import('layouts/MyLayout.vue'),
5 | children: [
6 | { path: '', component: () => import('pages/Index.vue') },
7 | {
8 | path: '/EasyField/:component',
9 | component: () => import('pages/EasyFieldDemo.vue'),
10 | props: true,
11 | },
12 | { path: '/new-demo', component: () => import('pages/NewDemo.vue') },
13 | { path: '/:schemaId', component: () => import('pages/EasyFormDemo.vue'), props: true },
14 | ],
15 | },
16 | ]
17 |
18 | export default routes
19 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/EfBtn.js:
--------------------------------------------------------------------------------
1 | export default {
2 | id: 'btn',
3 | component: 'EfBtn',
4 | btnLabel: 'Touch me',
5 | events: {
6 | click: (val, context) => console.log('@CLICK\nval → ', val, '\ncontext → ', context),
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/EfDiv.js:
--------------------------------------------------------------------------------
1 | export default {
2 | id: 'myRank',
3 | component: 'EfDiv',
4 | options: [
5 | { value: '1', label: 'one' },
6 | { value: '2', label: 'two' },
7 | ],
8 | prefix: 'Rank ',
9 | value: '1',
10 | }
11 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/EfMiniForm.js:
--------------------------------------------------------------------------------
1 | export default {
2 | component: 'EfMiniForm',
3 | label: 'Expenses',
4 | subLabel: "fill in this month's expenses",
5 | schema: [
6 | {
7 | component: 'QBtn',
8 | events: {
9 | click: (e, { $attrs, $parent }) => {
10 | const { rowIndex } = $attrs
11 | const { value = [] } = $parent
12 | const valueCopy = [...value]
13 | valueCopy.splice(rowIndex, 1)
14 | $parent.$emit('input', valueCopy)
15 | },
16 | },
17 | disable: (val, { $attrs, $parent }) => {
18 | const { rowIndex } = $attrs
19 | const { value = [] } = $parent
20 | return rowIndex === value.length
21 | },
22 | span: '26px',
23 | // component props:
24 | icon: 'remove_circle',
25 | color: 'negative',
26 | size: 'sm',
27 | flat: true,
28 | dense: true,
29 | },
30 | {
31 | id: 'type',
32 | label: 'Type',
33 | component: 'QSelect',
34 | // component props:
35 | emitValue: true,
36 | mapOptions: true,
37 | options: [
38 | { label: 'Home', value: 'home' },
39 | { label: 'Work', value: 'work' },
40 | ],
41 | },
42 | {
43 | id: 'amount',
44 | label: 'Amount',
45 | component: 'QInput',
46 | // component props:
47 | type: 'number',
48 | parseInput: Number,
49 | prefix: '€',
50 | },
51 | {
52 | id: 'paid for',
53 | label: 'Paid for',
54 | component: 'QToggle',
55 | default: false,
56 | span: 'auto',
57 | },
58 | ],
59 | default: () => [],
60 | }
61 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/QInput.js:
--------------------------------------------------------------------------------
1 | export default {
2 | id: 'myInput',
3 | label: 'My EasyField label',
4 | subLabel:
5 | 'This is a preview for all the props each field in a schema can have. Check the "props" section for the detailed documentation on each possible prop.',
6 | component: 'QInput',
7 | }
8 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/QSelectModel.js:
--------------------------------------------------------------------------------
1 | const formatForMarkdown = code => `\`\`\`js
2 | ${JSON.stringify(code, null, 2)}
3 | \`\`\``
4 |
5 | const createExample = (config, i) => [
6 | Object.assign(
7 | {
8 | component: 'QSelect',
9 | options: [
10 | { label: 'Self taught', value: 'self' },
11 | { label: 'By item', value: 'item' },
12 | { label: 'Mutation', value: 'mutation' },
13 | ],
14 | id: `example${i}`,
15 | },
16 | config
17 | ),
18 | {
19 | id: `example${i}-m`,
20 | component: 'Snarkdown',
21 | noLineNumbers: true,
22 | src: (val, { formData }) => formatForMarkdown(formData[`example${i}`]),
23 | evaluatedProps: ['src'],
24 | },
25 | {
26 | id: `example${i}-o`,
27 | component: 'Snarkdown',
28 | noLineNumbers: true,
29 | fieldClass: 'text-break-all',
30 | src: formatForMarkdown(config),
31 | },
32 | ]
33 |
34 | const exampleConfigs = [
35 | { span: true, label: 'Single select' },
36 | { component: 'QSelect' },
37 | { component: 'QSelect', emitValue: true },
38 | { component: 'QSelect', emitValue: true, mapOptions: true },
39 | { span: true, subLabel: 'Multiple select' },
40 | { component: 'QSelect', multiple: true },
41 | { component: 'QSelect', multiple: true, emitValue: true },
42 | { component: 'QSelect', multiple: true, emitValue: true, mapOptions: true },
43 | {
44 | span: true,
45 | label: 'Do not do this',
46 | subLabel: 'Select something and see why 😉',
47 | },
48 | { component: 'QSelect', multiple: true, emitValue: true },
49 | ]
50 |
51 | export default {
52 | actionButtons: [],
53 | mode: 'edit',
54 | columnCount: 3,
55 | schema: [
56 | {
57 | subLabel: 'Select some values for each QSelect field down below, and see what happens.',
58 | },
59 | {
60 | span: 1,
61 | label: 'Model:',
62 | },
63 | {
64 | span: 1,
65 | label: 'Options:',
66 | },
67 | ...exampleConfigs.reduce((carry, config, i) => {
68 | if (config.span === true) {
69 | carry.push(config)
70 | return carry
71 | }
72 | carry.push(...createExample(config, i))
73 | return carry
74 | }, []),
75 | ],
76 | }
77 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/actionButtons.js:
--------------------------------------------------------------------------------
1 | export default {
2 | mode: 'view',
3 | actionButtons: [
4 | 'delete',
5 | 'archive',
6 | 'cancel',
7 | 'edit',
8 | 'save',
9 | {
10 | component: 'EfBtn',
11 | btnLabel: 'log the data (check console)',
12 | push: true,
13 | events: {
14 | click: (event, { formData }) => console.log(formData),
15 | },
16 | },
17 | ],
18 | actionButtonDefaults: {
19 | archive: { icon: 'archive', showCondition: (_, { mode }) => mode !== 'edit' },
20 | delete: { icon: 'delete', showCondition: (_, { mode }) => mode !== 'edit' },
21 | edit: { icon: 'edit', flat: false, outline: true },
22 | save: { icon: 'save' },
23 | },
24 | schema: [
25 | {
26 | id: 'name',
27 | component: 'QInput',
28 | label: 'Superhero name',
29 | subLabel: 'Think of something cool.',
30 | },
31 | {
32 | id: 'powerOrigin',
33 | component: 'QBtnToggle',
34 | label: 'Power origin',
35 | subLabel: 'Where does your power come from?',
36 | // component props:
37 | options: [
38 | { label: 'Mutation', value: 'mutation' },
39 | { label: 'Self taught', value: 'self' },
40 | { label: 'By item', value: 'item' },
41 | ],
42 | spread: true,
43 | },
44 | ],
45 | }
46 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/advanced.js:
--------------------------------------------------------------------------------
1 | export default {
2 | columnCount: 4,
3 | actionButtons: ['delete', 'archive', 'cancel', 'edit', 'save'],
4 | schema: [
5 | {
6 | span: 3,
7 | },
8 | {
9 | span: 1,
10 | label: 'Example component:',
11 | },
12 | {
13 | id: 'name',
14 | span: 3,
15 | component: 'QInput',
16 | label: 'Superhero name',
17 | subLabel: 'Think of something cool.',
18 | required: true,
19 | },
20 | {
21 | id: 'md-input',
22 | component: 'Snarkdown',
23 | span: 1,
24 | // component props:
25 | noLineNumbers: true,
26 | src: "```js\n{component: 'QInput'}\n```",
27 | },
28 | {
29 | id: 'powerOrigin',
30 | span: 3,
31 | component: 'QBtnToggle',
32 | label: 'Power origin',
33 | subLabel: 'Where does your power come from?',
34 | // component props:
35 | spread: true,
36 | options: [
37 | { label: 'Mutation', value: 'mutation' },
38 | { label: 'Self taught', value: 'self' },
39 | { label: 'By item', value: 'item' },
40 | ],
41 | },
42 | {
43 | id: 'md-btn-toggle',
44 | component: 'Snarkdown',
45 | span: 1,
46 | // component props:
47 | noLineNumbers: true,
48 | src: "```js\n{component: 'QBtnToggle'}\n```",
49 | },
50 | {
51 | id: 'stamina',
52 | span: 3,
53 | component: 'QSlider',
54 | label: 'Stamina',
55 | default: 50,
56 | // component props:
57 | min: 0,
58 | max: 100,
59 | labelAlways: true,
60 | },
61 | {
62 | id: 'md-slider',
63 | component: 'Snarkdown',
64 | span: 1,
65 | // component props:
66 | noLineNumbers: true,
67 | src: "```js\n{component: 'QSlider'}\n```",
68 | },
69 | {
70 | id: 'power',
71 | span: 3,
72 | component: 'QInput',
73 | label: 'Power',
74 | subLabel: 'Fill in a number.',
75 | parseInput: Number,
76 | // component props:
77 | type: 'number',
78 | suffix: 'PW',
79 | },
80 | {
81 | id: 'md-input-nr',
82 | component: 'Snarkdown',
83 | span: 1,
84 | // component props:
85 | noLineNumbers: true,
86 | src:
87 | "```js\n{component: 'QInput'}\n```\n↳ But the input is saved as a number instead of a string.",
88 | },
89 | {
90 | id: 'roleModel',
91 | span: 3,
92 | component: 'QSelect',
93 | label: 'Role model',
94 | subLabel: 'Who do you look up to?',
95 | // component props:
96 | options: [
97 | { label: 'Steve Rogers/Captain America', value: 'captain-america' },
98 | { label: 'Tony Stark/Iron Man', value: 'iron-man' },
99 | { label: 'Thor Odinson', value: 'thor-odinson' },
100 | { label: 'Bruce Banner/The Incredible Hulk', value: 'the-incredible-hulk' },
101 | { label: 'Natasha Romanoff/Black Widow', value: 'black-widow' },
102 | { label: 'Clint Barton/Hawkeye', value: 'hawkeye' },
103 | { label: 'Pietro Maximoff/Quicksilver', value: 'quicksilver' },
104 | { label: 'Wanda Maximoff/Scarlet Witch', value: 'scarlet-witch' },
105 | { label: 'The Vision', value: 'the-vision' },
106 | { label: 'James Rhodes/War Machine (Iron Patriot)', value: 'war-machine' },
107 | { label: 'Sam Wilson/Falcon', value: 'falcon' },
108 | { label: 'Bucky Barnes/The Winter Soldier (White Wolf)', value: 'the-winter-soldier' },
109 | { label: "T'Challa/Black Panther", value: 'black-panther' },
110 | { label: 'Stephen Strange/Doctor Strange', value: 'doctor-strange' },
111 | { label: 'Peter Parker/Spider-Man', value: 'spider-man' },
112 | { label: 'Scott Lang/Ant-Man (Giant-Man)', value: 'ant-man' },
113 | { label: 'Hope van Dyne/Wasp', value: 'wasp' },
114 | { label: 'Carol Danvers/Captain Marvel', value: 'captain-marvel' },
115 | { label: 'Peter Quill/Star-Lord', value: 'star-lord' },
116 | { label: 'Gamora', value: 'gamora' },
117 | { label: 'Drax the Destroyer', value: 'drax-the-destroyer' },
118 | { label: 'Rocket (Raccoon)', value: 'rocket-raccoon' },
119 | { label: '(Baby, Teenage) Groot', value: 'groot' },
120 | { label: 'Mantis', value: 'mantis' },
121 | { label: 'Matthew Murdock/Daredevil', value: 'daredevil' },
122 | { label: 'Jessica Jones (Jewel)', value: 'jessica-jones' },
123 | { label: 'Carl Lucas/Luke Cage (Power Man)', value: 'luke-cage' },
124 | { label: 'Danny Rand/Iron Fist', value: 'iron-fist' },
125 | { label: 'Frank Castle/The Punisher', value: 'the-punisher' },
126 | ],
127 | },
128 | {
129 | id: 'md-select',
130 | component: 'Snarkdown',
131 | span: 1,
132 | // component props:
133 | noLineNumbers: true,
134 | src: "```js\n{component: 'QSelect'}\n```",
135 | },
136 | {
137 | id: 'checkboxes',
138 | span: 3,
139 | component: 'QOptionGroup',
140 | label: 'Check some boxes',
141 | default: () => [], // 'QOptionGroup' might not work without a default array
142 | // component props:
143 | type: 'checkbox',
144 | options: [
145 | {
146 | label: 'Option 1',
147 | value: 'op1',
148 | },
149 | {
150 | label: 'Option 2',
151 | value: 'op2',
152 | },
153 | {
154 | label: 'Option 3',
155 | value: 'op3',
156 | },
157 | ],
158 | },
159 | {
160 | id: 'md-option-group',
161 | component: 'Snarkdown',
162 | span: 1,
163 | // component props:
164 | noLineNumbers: true,
165 | src: "```js\n{component: 'QOptionGroup'}\n```",
166 | },
167 | {
168 | id: 'consent',
169 | component: 'QToggle',
170 | span: 3,
171 | label: 'Do you agree with our terms?',
172 | rules: [val => val || 'You must accept our terms'],
173 | default: false,
174 | },
175 | {
176 | id: 'md-toggle',
177 | component: 'Snarkdown',
178 | span: 1,
179 | // component props:
180 | noLineNumbers: true,
181 | src: "```js\n{component: 'QToggle'}\n```",
182 | },
183 | {
184 | id: 'submissionDate',
185 | span: 3,
186 | component: 'QInput',
187 | label: 'Date of submission',
188 | parseInput: val => new Date(val),
189 | valueType: 'date',
190 | dateFormat: 'YYYY/MM/DD', // see `EfDiv` documentation for more info on `dateFormat`
191 | // component props:
192 | mask: '####/##/##',
193 | placeholder: 'YYYY/MM/DD',
194 | },
195 | {
196 | id: 'md-input-date',
197 | component: 'Snarkdown',
198 | span: 1,
199 | // component props:
200 | noLineNumbers: true,
201 | src:
202 | "```js\n{component: 'QInput'}\n```\n↳ But the input is saved as a date instead of a string.",
203 | },
204 | ],
205 | }
206 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/basics.js:
--------------------------------------------------------------------------------
1 | export default {
2 | schema: [
3 | {
4 | id: 'name',
5 | component: 'QInput',
6 | label: 'Superhero name',
7 | subLabel: 'Think of something cool.',
8 | },
9 | {
10 | id: 'powerOrigin',
11 | component: 'QBtnToggle',
12 | label: 'Power origin',
13 | subLabel: 'Where does your power come from?',
14 | // component props:
15 | options: [
16 | { label: 'Mutation', value: 'mutation' },
17 | { label: 'Self taught', value: 'self' },
18 | { label: 'By item', value: 'item' },
19 | ],
20 | spread: true,
21 | },
22 | ],
23 | }
24 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/computedFields.js:
--------------------------------------------------------------------------------
1 | export default {
2 | columnCount: 3,
3 | schema: [
4 | {
5 | id: 'firstName',
6 | component: 'QInput',
7 | label: 'First name',
8 | },
9 | {
10 | id: 'lastName',
11 | component: 'QInput',
12 | label: 'Last name',
13 | },
14 | {
15 | id: 'fullName',
16 | component: 'QInput',
17 | label: 'Full name (computed)',
18 | disable: true,
19 | parseValue: (val, { formData }) =>
20 | `${formData.firstName || ''} ${formData.lastName || ''}`.trim(),
21 | },
22 | ],
23 | }
24 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/computedFields2.js:
--------------------------------------------------------------------------------
1 | export default {
2 | columnCount: 3,
3 | schema: [
4 | {
5 | id: 'firstName',
6 | component: 'QInput',
7 | label: 'First name',
8 | events: {
9 | input: (val, { formData, fieldInput }) => {
10 | const { lastName = '' } = formData
11 | const value = `${val} ${lastName}`.trim()
12 | fieldInput({ id: 'fullName', value })
13 | },
14 | },
15 | },
16 | {
17 | component: 'QInput',
18 | label: 'Last name',
19 | id: 'lastName',
20 | events: {
21 | input: (val, { formData, fieldInput }) => {
22 | const { firstName = '' } = formData
23 | const value = `${firstName} ${val}`.trim()
24 | fieldInput({ id: 'fullName', value })
25 | },
26 | },
27 | },
28 | {
29 | id: 'fullName',
30 | component: 'QInput',
31 | label: 'Full name (computed)',
32 | disable: true,
33 | },
34 | ],
35 | }
36 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/computedFields3.js:
--------------------------------------------------------------------------------
1 | export default {
2 | columnCount: 3,
3 | schema: [
4 | {
5 | id: 'firstName',
6 | component: 'QInput',
7 | label: 'First name',
8 | },
9 | {
10 | id: 'lastName',
11 | component: 'QInput',
12 | label: 'Last name',
13 | },
14 | {
15 | id: 'fullName',
16 | component: 'QInput',
17 | label: 'Full name (computed)',
18 | disable: true,
19 | parseValue: (val, { formData, fieldInput }) => {
20 | const value = `${formData.firstName || ''} ${formData.lastName || ''}`.trim()
21 | if (val !== value) fieldInput({ id: 'fullName', value })
22 | return value
23 | },
24 | },
25 | ],
26 | }
27 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/evaluatedProps1.js:
--------------------------------------------------------------------------------
1 | export default {
2 | mode: 'edit',
3 | actionButtons: [],
4 | columnCount: 1,
5 | schema: [
6 | {
7 | id: 'color',
8 | span: true,
9 | component: 'QBtnToggle',
10 | label: 'What is your favorite color?',
11 | evaluatedProps: ['subLabel'],
12 | subLabel: val =>
13 | val === 'red'
14 | ? 'like the sun'
15 | : val === 'blue'
16 | ? 'like the sky'
17 | : val === 'green'
18 | ? 'green is not a creative color'
19 | : val === 'other'
20 | ? 'oh, come on, just pick one'
21 | : 'pick a color!',
22 | // component props:
23 | options: [
24 | { label: 'red', value: 'red' },
25 | { label: 'blue', value: 'blue' },
26 | { label: 'green', value: 'green' },
27 | { label: 'other', value: 'other' },
28 | ],
29 | spread: true,
30 | },
31 | ],
32 | }
33 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/evaluatedProps2.js:
--------------------------------------------------------------------------------
1 | export default {
2 | mode: 'edit',
3 | actionButtons: [],
4 | columnCount: 2,
5 | schema: [
6 | {
7 | id: 'over18',
8 | component: 'QToggle',
9 | default: false,
10 | label: 'Are you over 18?',
11 | },
12 | {
13 | id: 'parentalConsent',
14 | component: 'QToggle',
15 | default: false,
16 | label: 'Do you have parental consent?',
17 | subLabel: 'This will be disabled when the first question is `true`.',
18 | evaluatedProps: ['disable'],
19 | // component props:
20 | disable: (val, { formData }) => formData.over18,
21 | },
22 | ],
23 | }
24 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/evaluatedProps3.js:
--------------------------------------------------------------------------------
1 | export default {
2 | mode: 'edit',
3 | actionButtons: ['edit', 'save'],
4 | columnCount: 2,
5 | schema: [
6 | {
7 | id: 'car',
8 | component: 'QToggle',
9 | default: false,
10 | label: 'Do you have a car?',
11 | },
12 | {
13 | id: 'carType',
14 | component: 'QInput',
15 | label: 'What is the brand?',
16 | subLabel: 'This is only shown when the first question is `true`.',
17 | evaluatedProps: ['showCondition'],
18 | showCondition: (val, { formData }) => formData.car,
19 | },
20 | {
21 | id: 'carNrPlate',
22 | component: 'QInput',
23 | label: 'Enter your license plate brand?',
24 | subLabel: "This is hidden when the form is set to 'view' mode. Try clicking 'save'.",
25 | evaluatedProps: ['showCondition'],
26 | showCondition: (val, { formData, mode }) => formData.car && mode === 'edit',
27 | },
28 | ],
29 | }
30 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/evaluatedProps4.js:
--------------------------------------------------------------------------------
1 | const carData = [
2 | { year: 2015, make: 'Audi', model: 'A3', trim: '2.0' },
3 | { year: 2015, make: 'Audi', model: 'A3', trim: '1.8' },
4 | { year: 2015, make: 'Audi', model: 'A6', trim: '2.5' },
5 | { year: 2015, make: 'Audi', model: 'A6', trim: '3.0' },
6 | { year: 2015, make: 'BMW', model: 'M3', trim: 'b2.0' },
7 | { year: 2015, make: 'BMW', model: 'M3', trim: 'b1.8' },
8 | { year: 2015, make: 'BMW', model: 'M5', trim: 'b2.5' },
9 | { year: 2015, make: 'BMW', model: 'M5', trim: 'b3.0' },
10 | { year: 2016, make: 'Chevy', model: 'Impala', trim: 'c2.0' },
11 | { year: 2016, make: 'Chevy', model: 'Impala', trim: 'c1.8' },
12 | { year: 2016, make: 'Chevy', model: 'Malibu', trim: 'c2.5' },
13 | { year: 2016, make: 'Chevy', model: 'Malibu', trim: 'c3.0' },
14 | { year: 2016, make: 'Dodge', model: 'RAM', trim: 'd2.0' },
15 | { year: 2016, make: 'Dodge', model: 'RAM', trim: 'd1.8' },
16 | { year: 2016, make: 'Dodge', model: 'Challanger', trim: 'd2.5' },
17 | { year: 2016, make: 'Dodge', model: 'Challanger', trim: 'd3.0' },
18 | ]
19 |
20 | const uniqueValues = array => [...new Set(array)]
21 | const mapForQSelect = value => ({ value, label: value })
22 |
23 | const clearFields = (fieldIds, fieldInput) => {
24 | fieldIds.forEach(id => fieldInput({ id, value: '' }))
25 | }
26 |
27 | export default {
28 | mode: 'edit',
29 | actionButtons: ['cancel', 'edit', 'save'],
30 | columnCount: 4,
31 | schema: [
32 | {
33 | id: 'year',
34 | label: 'Year',
35 | component: 'QSelect',
36 | events: {
37 | // clear fields right from input to prevent invalid data
38 | input: (val, { fieldInput }) => clearFields(['make', 'model', 'trim'], fieldInput),
39 | },
40 | // component props:
41 | options: uniqueValues(carData.map(d => d.year)).map(mapForQSelect),
42 | emitValue: true,
43 | },
44 | {
45 | id: 'make',
46 | label: 'Make',
47 | component: 'QSelect',
48 | evaluatedProps: ['options'],
49 | events: {
50 | // clear fields right from input to prevent invalid data
51 | input: (val, { fieldInput }) => clearFields(['model', 'trim'], fieldInput),
52 | },
53 | // component props:
54 | options: (val, { formData }) => {
55 | const { year } = formData || {}
56 | return uniqueValues(carData.filter(car => car.year === year).map(d => d.make)).map(
57 | mapForQSelect
58 | )
59 | },
60 | emitValue: true,
61 | },
62 | {
63 | id: 'model',
64 | label: 'Model',
65 | component: 'QSelect',
66 | evaluatedProps: ['options'],
67 | events: {
68 | // clear fields right from input to prevent invalid data
69 | input: (val, { fieldInput }) => clearFields(['trim'], fieldInput),
70 | },
71 | // component props:
72 | options: (val, { formData }) => {
73 | const { year, make } = formData || {}
74 | return uniqueValues(
75 | carData.filter(car => car.year === year && car.make === make).map(d => d.model)
76 | ).map(mapForQSelect)
77 | },
78 | emitValue: true,
79 | },
80 | {
81 | id: 'trim',
82 | label: 'Trim',
83 | component: 'QSelect',
84 | evaluatedProps: ['options'],
85 | // component props:
86 | options: (val, { formData }) => {
87 | const { year, make, model } = formData || {}
88 | return uniqueValues(
89 | carData
90 | .filter(car => car.year === year && car.make === make && car.model === model)
91 | .map(d => d.trim)
92 | ).map(mapForQSelect)
93 | },
94 | emitValue: true,
95 | },
96 | ],
97 | }
98 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/events1.js:
--------------------------------------------------------------------------------
1 | export default {
2 | mode: 'edit',
3 | actionButtons: [],
4 | columnCount: 1,
5 | schema: [
6 | {
7 | id: 'testField',
8 | component: 'QInput',
9 | label: 'Type something',
10 | events: {
11 | input: (val, { $q }) => $q.notify(val),
12 | focus: (val, { id, label, $q }) => $q.notify(`focussed: 「${label}」 (id: ${id})`),
13 | },
14 | },
15 | ],
16 | }
17 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/events2.js:
--------------------------------------------------------------------------------
1 | export default {
2 | mode: 'edit',
3 | actionButtons: [],
4 | columnCount: 2,
5 | schema: [
6 | {
7 | id: 'tel',
8 | component: 'QInput',
9 | label: 'Phone nr (hyphenated)',
10 | subLabel: 'Type any number with `-` or `( )`',
11 | events: {
12 | input: (val, { fieldInput, formData }) =>
13 | fieldInput({ id: 'telClean', value: !val ? '' : val.replace(/[^\d]/g, '').trim() }),
14 | },
15 | },
16 | {
17 | id: 'telClean',
18 | component: 'QInput',
19 | label: 'Phone nr (only numbers)',
20 | subLabel: 'This field is automatically updated when you type in a phone nr on the left.',
21 | // component props:
22 | disable: true,
23 | },
24 | ],
25 | }
26 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/index.js:
--------------------------------------------------------------------------------
1 | // easy forms
2 | export const basics = {
3 | code: [require('./basics').default],
4 | string: [require('!!raw-loader!./basics').default],
5 | }
6 | export const actionButtons = {
7 | code: [require('./actionButtons').default],
8 | string: [require('!!raw-loader!./actionButtons').default],
9 | }
10 | export const advanced = {
11 | code: [require('./advanced').default],
12 | string: [require('!!raw-loader!./advanced').default],
13 | }
14 | export const nestedData = {
15 | code: [require('./nestedData').default],
16 | string: [require('!!raw-loader!./nestedData').default],
17 | }
18 | export const validation = {
19 | code: [require('./validation').default, require('./validation2').default],
20 | string: [
21 | require('!!raw-loader!./validation').default,
22 | require('!!raw-loader!./validation2').default,
23 | ],
24 | }
25 | export const responsiveStyle = {
26 | code: [require('./responsiveStyle').default],
27 | string: [require('!!raw-loader!./responsiveStyle').default],
28 | }
29 | export const QSelectModel = {
30 | code: [require('./QSelectModel').default],
31 | string: [require('!!raw-loader!./QSelectModel').default],
32 | }
33 | export const evaluatedProps = {
34 | code: [
35 | require('./evaluatedProps1').default,
36 | require('./evaluatedProps2').default,
37 | require('./evaluatedProps3').default,
38 | require('./evaluatedProps4').default,
39 | ],
40 | string: [
41 | require('!!raw-loader!./evaluatedProps1').default,
42 | require('!!raw-loader!./evaluatedProps2').default,
43 | require('!!raw-loader!./evaluatedProps3').default,
44 | require('!!raw-loader!./evaluatedProps4').default,
45 | ],
46 | }
47 | export const events = {
48 | code: [require('./events1').default, require('./events2').default],
49 | string: [require('!!raw-loader!./events1').default, require('!!raw-loader!./events2').default],
50 | }
51 | export const computedFields = {
52 | code: [
53 | require('./computedFields').default,
54 | require('./computedFields2').default,
55 | require('./computedFields3').default,
56 | ],
57 | string: [
58 | require('!!raw-loader!./computedFields').default,
59 | require('!!raw-loader!./computedFields2').default,
60 | require('!!raw-loader!./computedFields3').default,
61 | ],
62 | }
63 |
64 | // easy fields
65 | export const QInput = {
66 | code: [require('./QInput').default],
67 | string: [require('!!raw-loader!./QInput').default],
68 | }
69 | export const EfMiniForm = {
70 | code: [require('./EfMiniForm').default],
71 | string: [require('!!raw-loader!./EfMiniForm').default],
72 | }
73 | export const EfBtn = {
74 | code: [require('./EfBtn').default],
75 | string: [require('!!raw-loader!./EfBtn').default],
76 | }
77 | export const EfDiv = {
78 | code: [require('./EfDiv').default],
79 | string: [require('!!raw-loader!./EfDiv').default],
80 | }
81 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/nestedData.js:
--------------------------------------------------------------------------------
1 | export default {
2 | actionButtons: [],
3 | mode: 'edit',
4 | columnCount: 3,
5 | events: {
6 | 'field-input': val => {
7 | console.log('logging @field-input payload:', val)
8 | },
9 | },
10 | schema: [
11 | {
12 | id: 'size.width',
13 | label: 'Width',
14 | component: 'QInput',
15 | parseInput: Number,
16 | // component props:
17 | type: 'number',
18 | suffix: 'cm',
19 | },
20 | {
21 | id: 'size.depth',
22 | label: 'Depth',
23 | component: 'QInput',
24 | parseInput: Number,
25 | // component props:
26 | type: 'number',
27 | suffix: 'cm',
28 | },
29 | {
30 | id: 'size.height',
31 | label: 'Height',
32 | component: 'QInput',
33 | parseInput: Number,
34 | // component props:
35 | type: 'number',
36 | suffix: 'cm',
37 | },
38 | ],
39 | }
40 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/responsiveStyle.js:
--------------------------------------------------------------------------------
1 | export default []
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/validation.js:
--------------------------------------------------------------------------------
1 | export default {
2 | columnCount: 3,
3 | actionButtons: ['delete', 'archive', 'cancel', 'edit', 'save'],
4 | schema: [
5 | {
6 | id: 'name',
7 | label: 'Name',
8 | component: 'QInput',
9 | // component props:
10 | required: true,
11 | },
12 | {
13 | id: 'age',
14 | label: 'Age',
15 | component: 'QInput',
16 | parseInput: Number,
17 | rules: [val => val >= 18 || 'You must be over 18'],
18 | // component props:
19 | type: 'number',
20 | },
21 | {
22 | id: 'consent',
23 | label: 'Do you agree with our terms?',
24 | component: 'QToggle',
25 | rules: [val => val || 'You must accept our terms'],
26 | default: false,
27 | },
28 | ],
29 | }
30 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/examples/validation2.js:
--------------------------------------------------------------------------------
1 | import { validateFormPerSchema } from 'ui'
2 | // You must write:
3 | // import { validateFormPerSchema } from 'quasar-ui-easy-forms'
4 |
5 | const data = {
6 | name: undefined,
7 | age: undefined,
8 | consent: undefined,
9 | }
10 |
11 | const schema = [
12 | {
13 | id: 'name',
14 | label: 'Name',
15 | component: 'QInput',
16 | required: true,
17 | },
18 | {
19 | id: 'age',
20 | label: 'Age',
21 | component: 'QInput',
22 | type: 'number',
23 | parseInput: Number,
24 | rules: [val => val >= 18 || 'You must be over 18'],
25 | },
26 | {
27 | id: 'consent',
28 | label: 'Do you agree with our terms?',
29 | component: 'QToggle',
30 | default: false,
31 | rules: [val => val || 'You must accept our terms'],
32 | },
33 | ]
34 |
35 | export default {
36 | columnCount: 2,
37 | actionButtons: ['delete', 'archive', 'cancel', 'edit', 'save'],
38 | schema: [
39 | {
40 | component: 'EfBtn',
41 | btnLabel: 'validate',
42 | subLabel: 'Click this and check the developer tools > console.',
43 | events: {
44 | click: e => {
45 | const result = validateFormPerSchema(data, schema)
46 | console.log('result → ', result)
47 | },
48 | },
49 | },
50 | {
51 | component: 'EfBtn',
52 | btnLabel: 'set data',
53 | subLabel: 'Click this to set the correct data and then try "validate" again!',
54 | events: {
55 | click: e => {
56 | data.name = 'name'
57 | data.age = 18
58 | data.consent = true
59 | },
60 | },
61 | },
62 | ],
63 | }
64 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/pages/QSelectModel.js:
--------------------------------------------------------------------------------
1 | import merge from 'merge-anything'
2 |
3 | const description = `This is an example of an \`\` with a bunch of evaluated field props.
4 |
5 | In this example I display all the possibilities to manage your field "model" with a QSelect field. I always found this confusing in the Quasar docs, so here is a nice overview you can play around with.
6 |
7 | At the same time, if you check the source code, you'll see how easy it is to create such a complex form with such an easy schema. 🙃`
8 |
9 | export default {
10 | mode: 'edit',
11 | actionButtons: [],
12 | schema: [
13 | {
14 | component: 'Snarkdown',
15 | noLineNumbers: true,
16 | src: description,
17 | },
18 | ],
19 | }
20 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/pages/actionButtons.js:
--------------------------------------------------------------------------------
1 | const description = `
2 | Action buttons are buttons you would want to add to a form to do things like edit/save/delete etc... You can easily add action buttons like these on the top/bottom or sides of your EasyForm.
3 |
4 | Action buttons are set via the prop 'actionButtons'. You can use pre-made action buttons that emit events. You can also overwrite the look of these pre-made buttons. Finally you can also set custom buttons and fields.
5 |
6 | ### Pre-made action buttons
7 |
8 | Pre-made buttons can be added to your form by just passing the string of the button you want:
9 |
10 | \`:action-buttons="['edit', 'cancel', 'save', 'delete', 'archive']"\`
11 |
12 | When added you will see the buttons like the preview below. They each have a functionality:
13 |
14 | - \`'edit'\`: Adds a button that puts the form in "edit" mode
15 | - \`'cancel'\`: Adds a button that puts the form back into "view" mode & reverts the content to its original state
16 | - \`'save'\`: Adds a button that puts the form back into "view" mode & keeps the modified content
17 | - \`'delete'\` & \`'archive'\`: Adds a button that emits a delete or archive event (you must implement your own logic)
18 |
19 | The buttons above emits the events: \`@edit\`, \`@cancel\`, \`@save\`, \`@delete\`, \`@archive\`
20 |
21 | You can listen for these events on the \`\` to use do things like:
22 | - saving data to a DB when "save" is clicked
23 | - moving a popup when 'cancel' is clicked
24 | - clear the form data (\`value\`) when 'delete' is clicked
25 |
26 | The \`@save\` event receives a payload with the new and old form data.
27 |
28 | - \`@save="onSave"\`
29 |
30 | \`\`\`js
31 | {
32 | onSave ({newData, oldData}) {
33 | console.log(newData) // an object with only the updated fields
34 | console.log(oldData) // the original object with all the field values
35 | // if you need a combination of both:
36 | const newFormData = {...oldData, ...newData}
37 | }
38 | }
39 | \`\`\`
40 |
41 | ### Overwriting pre-made buttons
42 |
43 | You can overwrite how the pre-made buttons look by providing an object like so:
44 |
45 | \`\`\`js
46 | {
47 | 'edit': { icon: 'edit' },
48 | 'save': { push: true },
49 | 'delete': { color: 'secondary' }
50 | }
51 | \`\`\`
52 |
53 | ### Providing custom buttons & fields
54 |
55 | You can also pass custom buttons & fields with a schema. The schema you provide works just like the EasyForm schema.
56 |
57 | An example of a custom button could be:
58 | \`\`\`js
59 | actionButtons: [{
60 | component: 'EfBtn',
61 | btnLabel: 'log the data',
62 | showCondition: (_, {formData}) => formData.enableLogging,
63 | events: {
64 | click: (event, {formData}) => console.log(formData),
65 | },
66 | }]
67 | \`\`\`
68 |
69 | Being able to show/hide these button based on the \`formData\` can be very powerful.
70 | Be sure to check out the "Evaluated Props" and "Events" documentation.
71 | `
72 |
73 | export default {
74 | mode: 'edit',
75 | actionButtons: [],
76 | schema: [
77 | {
78 | component: 'Snarkdown',
79 | noLineNumbers: true,
80 | src: description,
81 | },
82 | ],
83 | }
84 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/pages/advanced.js:
--------------------------------------------------------------------------------
1 | const description = `This is a more advanced example so you can see the full power of EasyForms. All fields you see here are just regular Quasar components.
2 |
3 | You can use any Quasar component, but you need to be sure to register the components in \`quasar.conf.js\`.
4 | \`\`\`js
5 | {
6 | framework: {
7 | // you can use auto for components other than those you use in EasyForm
8 | all: 'auto',
9 |
10 | // you need to register components you use in EasyForm because Quasar can't auto detect them
11 | components: ['QInput'],
12 | }
13 | }
14 | \`\`\`
15 |
16 | You can check the source code of the example to see what kind of properties are used in the schema to generate this advanced form. Something you can notice is that the form's v-model is updated with the default values specified in the form's schema.
17 | `
18 |
19 | export default {
20 | mode: 'edit',
21 | actionButtons: [],
22 | schema: [
23 | {
24 | component: 'Snarkdown',
25 | noLineNumbers: true,
26 | src: description,
27 | },
28 | ],
29 | }
30 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/pages/basics.js:
--------------------------------------------------------------------------------
1 | const description = `\`\` is a component that allows you to easily create forms by passing an object with a schema on how you want the form to look. By merely passing a schema array you can easily generate entire forms! No more writing clunky HTML forms! 🎉
2 |
3 | #### **Schema**
4 |
5 | The schema of an EasyForm is an array of objects that usually looks something like this:
6 | Eg.:
7 | \`\`\`js
8 | [
9 | // you can use custom Vue components
10 | { id: 'myField', label: 'My Field', component: 'MyFieldVueComponent' },
11 |
12 | // you can use quasar components
13 | { id: 'name', label: 'Name', component: 'QInput' },
14 | ]
15 | \`\`\`
16 |
17 | Besides 'id', 'label' and 'component' there are many more props you can pass: subLabel; required; labelPosition; fieldClasses; componentClasses; parseValue; parseInput; events and many more.
18 |
19 | #### **Value/Model**
20 |
21 | There are several ways to work with the data of an EasyForm.
22 |
23 | 1. You can pass an empty object as \`v-model\` (or \`:value\` & listen to \`@input\`)
24 | In this case EasyForms will populate an object with the field's \`id\` as key and the user input as value.
25 |
26 | 2. You can listen to the \`@field-input\` event which triggers every time a field's value changes. It's payload is an object that looks like: \`{id, value}\`.
27 |
28 | 3. You can listen to the \`@save\` event which is triggered when the form's save button is pressed. It's payload is an object that looks like: \`{newData, oldData}\`. For more info see the [Action Buttons documentation](/actionButtons).
29 |
30 | #### **Mode**
31 |
32 | EasyForm has four modes:
33 | - \`'view'\`: Show fields based on the schema; but make each field \`readonly: true\`.
34 | - \`'edit'\`: Show editable fields based on the schema
35 | - \`'add'\`: The same as 'edit'
36 | - \`'raw'\`: Used to show raw data of your form. No fields are generated, just divs with the labels and values. This mode is powerful because it will automatically map values to the schema provided (eg. adding pre-/suffix; mapping to options of a select; etc.)
37 |
38 | When in 'view' mode, EasyForm can be used to just show data. These buttons can also be disabled and by providing a custom mode you can keep the form in a certain mode forever.
39 |
40 | --
41 |
42 | Be sure to check out all the other pages for more functionality, but first...
43 |
44 | Try filling in the form below and play around with the props via the **interactive preview**. You can click "props" to update any props used in the and see how the how the preview and source code changes.`
45 |
46 | export default {
47 | mode: 'edit',
48 | actionButtons: [],
49 | schema: [
50 | {
51 | component: 'Snarkdown',
52 | noLineNumbers: true,
53 | src: description,
54 | },
55 | ],
56 | }
57 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/pages/computedFields.js:
--------------------------------------------------------------------------------
1 | const description = `Computed fields are fields that can represent data which doesn't neccesarily exist in your data. They have a "caluculated value" based on the form data.
2 |
3 | Do not confuse this concept with "Evaluated Props".
4 | - Evaluated Props: a calculated prop of a field
5 | - Computed Fields: a field with a calculated value
6 |
7 | An example of a Computed Field could be a full name of a person which exists of \`\${formData.firstName} \${formData.lastName}\`
8 |
9 | There are three ways we could create such a field:
10 | `
11 |
12 | export default {
13 | mode: 'edit',
14 | actionButtons: [],
15 | schema: [
16 | {
17 | component: 'Snarkdown',
18 |
19 | noLineNumbers: true,
20 | src: description,
21 | },
22 | {
23 | id: 'chosenExample',
24 | component: 'QBtnToggle',
25 | spread: true,
26 | noCaps: true,
27 | unelevated: true,
28 | options: [
29 | { label: 'The parseValue prop', value: 0 },
30 | { label: 'Update via fieldInput', value: 1 },
31 | { label: 'Combine parseValue & fieldInput', value: 2 },
32 | ],
33 | },
34 | {
35 | component: 'Snarkdown',
36 |
37 | showCondition: (value, { formData }) => formData.chosenExample === 0,
38 | noLineNumbers: true,
39 | src: `
40 | ### The \`parseValue\` prop
41 |
42 | \`\`\`js
43 | {
44 | id: 'fullName',
45 | component: 'QInput', // or any other component
46 | parseValue: (val, {formData}) => \`\${formData.firstName || ''} \${formData.lastName || ''}\`
47 | }
48 | \`\`\`
49 |
50 | So even though the field \`fullName\` has no \`value\` at all, it will always stay in sync with the current \`formData\`.
51 |
52 | When implementing a computed field this way however, \`fullName\` will never have that computed value emitted. This means that it won't be included in the EasyForm events: \`@input\`, \`@field-input\` and \`@save\`. So it's difficult to capture and save this calculated value alongside your other data. See the next tab for another method.
53 | `.trim(),
54 | },
55 | {
56 | component: 'Snarkdown',
57 |
58 | showCondition: (value, { formData }) => formData.chosenExample === 1,
59 | noLineNumbers: true,
60 | src: `
61 | ### Update via \`fieldInput\`
62 |
63 | It can be handy to also save the calculated value in your database so you can filter/search/sort on this field. (This is required when using eg. an [EasyTable](https://quasar-easy-tables.web.app) or QTable.)
64 |
65 | In this case we can use the method called \`fieldInput()\` which is accessible on the context and first explained on the [events documentation page](/events).
66 |
67 | \`\`\`js
68 | {
69 | id: 'firstName',
70 | events: {
71 | input: (val, {formData, fieldInput}) => {
72 | const { lastName = '' } = formData
73 | const value = \`\${val} \${lastName}\`.trim()
74 | fieldInput({id: 'fullName', value})
75 | }
76 | },
77 | },
78 | {
79 | id: 'lastName',
80 | events: {
81 | input: (val, {formData, fieldInput}) => {
82 | const { firstName = '' } = formData
83 | const value = \`\${firstName} \${val}\`.trim()
84 | fieldInput({id: 'fullName', value})
85 | }
86 | },
87 | }
88 | \`\`\`
89 |
90 | This method has pro's and con's though:
91 |
92 | - PRO: you don't need to include the Computed Field (\`fullName\`) on the form at all
93 | - CON: this is quite verbose...
94 | - CON: it cannot be used if you need a computed field _not_ based on other fields (eg. a timestamp returning \`new Date()\`)
95 | - CON: you cannot use this method to add a new "caluculated field" at a later time, when your database already has some data
96 |
97 | There is also a third way we can create a computed field (see the last tab).
98 | `.trim(),
99 | },
100 | {
101 | component: 'Snarkdown',
102 |
103 | showCondition: (value, { formData }) => formData.chosenExample === 2,
104 | noLineNumbers: true,
105 | src: `
106 | ### Combine \`parseValue\` & \`fieldInput\`
107 |
108 | The third way to create a computed field is this:
109 |
110 | \`\`\`js
111 | {
112 | id: 'fullName',
113 | component: 'QInput', // or any other component
114 | parseValue: (val, {formData, fieldInput}) => {
115 | const value = \`\${formData.firstName || ''} \${formData.lastName || ''}\`.trim()
116 | if (val !== value) fieldInput({id: 'fullName', value})
117 | return value
118 | },
119 | // If you want to hide the computed field you can set:
120 | // showCondition: false
121 | }
122 | \`\`\`
123 |
124 | Basically you write your logic inside the \`parseValue\` prop of your computed field, and also trigger a \`fieldInput\` action from within here.
125 |
126 | However, as the more experienced developers will notice...
127 | :::
128 | This is the same as introducing a side-effect to a computed property! By design this is discouraged, so isn't this bad?
129 | :::
130 |
131 | I say "nay". The reason it is discouraged is because side-effects to computed properties that modify data are impossible to track. In a few months if you don't know why a certain value is being modified, you'll have a hard time finding eventually it was the side-effect from a computed property.
132 |
133 | If we understand this reason, then in our case, it is perfectly valid to do so, because we are only modifying the data of the field we are describing right there. We are simply doing something equivalent to triggering a \`emit('input', val)\` on a component manually, nothing wrong with that.
134 |
135 | However, keep in mind that also this method has its own pro's and con's:
136 | - PRO: it can be used as stand-alone, without relying on other fields & without the need to render other fields
137 | - PRO: because it just uses \`parseValue\` it's less verbose (opposed to listening to input events of other fields)
138 | - PRO: the logic for this field is contained in its own options object
139 | - PRO: even if your database already has data, a computed field like this can be added at a later date
140 | - CON: you have to include this "Computed Field" in all forms the user can edit the related fields (and probably with \`showCondition: false\`)
141 | `.trim(),
142 | },
143 | ],
144 | }
145 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/pages/evaluatedProps.js:
--------------------------------------------------------------------------------
1 | const description = `## Evaluated Props
2 |
3 | As you know, \`\` needs a 'schema' with information on each field you want to show. However, when using an EasyForm you can replace the value of any prop in any field with a function instead of the value directly. This function will be executed any time the data of any field changes. This allows you to have "dynamic" props, based on the data of the form.
4 |
5 | For example, when you pass \`disable: true\` to a certain field, it will appear as disabled inside the form. You can instead also pass \`disable: (val, context) => context.formData.myCheckBox\` to be able to only disable that field when \`myCheckBox\` is truthy.
6 |
7 | Evaluated props will receive 2 params: \`(val, context)\`.
8 | - \`val\` is the current value of the field
9 | - \`context\` is the Vue component reference of the \`\`, you can deconstruct this to access any other properties/values.
10 |
11 | The most important props you can access from \`context\`:
12 | - \`formData\` This is the *nested* data of all the fields inside an EasyForm.
13 | - \`formDataFlat\` This is the *flattened* data of all the fields inside an EasyForm.
14 | - \`mode\` The current mode of the EasyForm. Can be \`'view'\` | \`'edit'\` | \`'add'\` | \`raw\`
15 | - \`formId\` An 'id' of the EasyForm. This is only present when manually set.
16 | - Other common Vue props like: \`$store\`, \`$router\`, \`$q\` (for Quasar apps) etc.
17 |
18 | Try to refrain from accessing props other than the ones listed above, because these are mainly used internal and could have behaviour changes that could break your app.
19 | `
20 |
21 | export default {
22 | mode: 'edit',
23 | actionButtons: [],
24 | schema: [
25 | {
26 | component: 'Snarkdown',
27 | noLineNumbers: true,
28 | src: description,
29 | },
30 | {
31 | id: 'chosenExample',
32 | component: 'QBtnToggle',
33 | spread: true,
34 | noCaps: true,
35 | unelevated: true,
36 | options: [
37 | { label: 'Dynamic prop based on the value of the field', value: 0 },
38 | { label: 'Dynamic prop based on the value of "another" field', value: 1 },
39 | { label: 'Dynamic "conditional rendering" of a field', value: 2 },
40 | { label: 'Dynamic "options" of a select-field', value: 3 },
41 | ],
42 | },
43 | {
44 | component: 'Snarkdown',
45 | noLineNumbers: true,
46 | showCondition: (value, { formData }) => formData.chosenExample === 0,
47 | src: `
48 | ### Dynamic prop based on the value of the field
49 | Eg. \`subLabel: val => val === 'purple' ? 'nice!' : 'choose a color'\`
50 | `.trim(),
51 | },
52 | {
53 | component: 'Snarkdown',
54 | noLineNumbers: true,
55 | showCondition: (value, { formData }) => formData.chosenExample === 1,
56 | src: `
57 | ### Dynamic prop based on the value of "another" field
58 | Eg. \`disable: (val, {formData}) => formData.over18\`
59 | `.trim(),
60 | },
61 | {
62 | component: 'Snarkdown',
63 | noLineNumbers: true,
64 | showCondition: (value, { formData }) => formData.chosenExample === 2,
65 | src: `
66 | ### Dynamic "conditional rendering" of a field
67 |
68 | Eg. \`showCondition: (val, {formData}) => formData.car\`
69 |
70 | \`showCondition\` is a special prop that can only be used inside the schema of an EasyForm.
71 | `.trim(),
72 | },
73 | {
74 | component: 'Snarkdown',
75 | noLineNumbers: true,
76 | showCondition: (value, { formData }) => formData.chosenExample === 3,
77 | src: `
78 | ### Dynamic "options" of a select-field
79 | `.trim(),
80 | },
81 | ],
82 | }
83 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/pages/events.js:
--------------------------------------------------------------------------------
1 | const description = `Each \`\` can be passed an prop called \`events\`. This prop can be passed like so: \`:events="events"\` and will set all the event listeners on the field like so: \`v-on="events"\`.
2 |
3 | An \`events\` prop would look like so:
4 | \`\`\`js
5 | events: {
6 | input: ($event, context) => { /* do something */ },
7 | focus: ($event, context) => { /* do something */ },
8 | // etc...
9 | }
10 | \`\`\`
11 |
12 | The benefits of passing your event listeners via the \`events\` prop are:
13 | - Besides the typical \`$event\` parameter they receive as first parameter, they will receive a second \`context\` parameter.
14 | - \`context\` is the Vue component reference of the \`\`, you can deconstruct this to access any other properties/values.
15 | - \`context\` has useful props like: \`$store\`, \`$router\`, \`formData\`, \`formDataFlat\`, \`mode\`, \`formId\`, ... All of these are explained in the *"Evaluated Props" documentation*, so be sure to check that.
16 | - \`context\` has a special function called \`fieldInput\` which can be used to modify other fields programatically.
17 | - And all this can be set from inside an \`\`'s \`schema\` so you don't need add anything manually inside your templates.
18 |
19 | Phew. That was a bit of a lot of information all at once. 😅 Let's look at some examples:
20 | `
21 |
22 | export default {
23 | mode: 'edit',
24 | actionButtons: [],
25 | schema: [
26 | {
27 | component: 'Snarkdown',
28 | noLineNumbers: true,
29 | src: description,
30 | },
31 | {
32 | id: 'chosenExample',
33 | component: 'QBtnToggle',
34 | spread: true,
35 | noCaps: true,
36 | unelevated: true,
37 | options: [
38 | { label: 'Notify on events', value: 0 },
39 | { label: "Update other fields on 'input'", value: 1 },
40 | ],
41 | },
42 | {
43 | component: 'Snarkdown',
44 | showCondition: (value, { formData }) => formData.chosenExample === 0,
45 | noLineNumbers: true,
46 | src: `
47 | ### Notify on events
48 |
49 | Here we see an example of two events being used.
50 | \`\`\`js
51 | events: {
52 | input: (val, {$q}) => $q.notify(val)
53 | focus: (val, {id, label, $q}) => $q.notify(\`focussed: 「\${label}」 (id: \${id})\`)
54 | }
55 | \`\`\``.trim(),
56 | },
57 | {
58 | component: 'Snarkdown',
59 | showCondition: (value, { formData }) => formData.chosenExample === 1,
60 | noLineNumbers: true,
61 | src: `
62 | ### Update other fields on 'input'
63 | Here we see an example of one field updating the contents of another on the input event.
64 | \`\`\`js
65 | events: {
66 | input: (val, {fieldInput}) => {
67 | // get only digits from input
68 | const value = !val ? '' : val.replace(/[^\d]/g, '').trim()
69 |
70 | // set field 'telClean' to this value
71 | fieldInput({id: 'telClean', value})
72 | }
73 | }
74 | \`\`\`
75 |
76 | The \`fieldInput\` function can be used to update other fields inside your form. It receives a single parameter which should be an object that looks like \`{id, value}\` with the \`id\` of the field you want to update and a \`value\` you want to update it with.
77 |
78 | Be sure to also check the documentation on \`Computed Fields\`.
79 | `.trim(),
80 | },
81 | ],
82 | }
83 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/pages/index.js:
--------------------------------------------------------------------------------
1 | export { default as actionButtons } from './actionButtons'
2 | export { default as basics } from './basics'
3 | export { default as advanced } from './advanced'
4 | export { default as evaluatedProps } from './evaluatedProps'
5 | export { default as events } from './events'
6 | export { default as nestedData } from './nestedData'
7 | export { default as responsiveStyle } from './responsiveStyle'
8 | export { default as validation } from './validation'
9 | export { default as computedFields } from './computedFields'
10 | export { default as QSelectModel } from './QSelectModel'
11 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/pages/nestedData.js:
--------------------------------------------------------------------------------
1 | const description = `An \`\` can use a nested data structure on a flat form schema. When you have a nested data structure you will need to appoint each field ID with dot notation.
2 |
3 | Eg. a field with ID \`size.width\` points to \`{size: {width}}\` in your data.
4 |
5 | Besides writing your field IDs with dot notation, nothing further needs to be done.
6 |
7 | The only thing you need to be careful with is the \`@field-input\` event:
8 | - Listening to the \`@input\` event will always return the full data nested
9 | - Listening to the \`@field-input\` event will always have the field ID with dot-notation in its payload.
10 |
11 | In the example below you can see a nested data structure when updating one of the fields, but if you check the developer tools > console, you will see that the \`id\`s are logged as eg. \`'size.width'\``
12 |
13 | export default {
14 | mode: 'edit',
15 | actionButtons: [],
16 | schema: [
17 | {
18 | component: 'Snarkdown',
19 | noLineNumbers: true,
20 | src: description,
21 | },
22 | ],
23 | }
24 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/pages/responsiveStyle.js:
--------------------------------------------------------------------------------
1 | const description = ``
2 |
3 | export default {
4 | mode: 'edit',
5 | actionButtons: [],
6 | schema: [
7 | {
8 | component: 'Snarkdown',
9 | noLineNumbers: true,
10 | src: description,
11 | },
12 | ],
13 | }
14 |
--------------------------------------------------------------------------------
/ui/dev/src/schemas/pages/validation.js:
--------------------------------------------------------------------------------
1 | const description = `EasyForms have validation enabled by default when clicking the save button or when executing \`validate\` on the EasyForm ref.
2 |
3 | There is also the possibility to do programatic validation. EasyForms provides a helper function which can be used without the need of rendering the form at all. It can be used like so:
4 | \`\`\`js
5 | import { validateFormPerSchema } from 'quasar-ui-easy-forms'
6 |
7 | validateFormPerSchema(formData, schema)
8 | \`\`\``
9 |
10 | export default {
11 | mode: 'edit',
12 | actionButtons: [],
13 | schema: [
14 | {
15 | component: 'Snarkdown',
16 |
17 | noLineNumbers: true,
18 | src: description,
19 | },
20 | {
21 | id: 'chosenExample',
22 | component: 'QBtnToggle',
23 | spread: true,
24 | noCaps: true,
25 | unelevated: true,
26 | options: [
27 | { label: 'Basic validation', value: 0 },
28 | { label: 'Programatic validation', value: 1 },
29 | ],
30 | },
31 | ],
32 | }
33 |
--------------------------------------------------------------------------------
/ui/dev/src/statics/app-logo-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/app-logo-128x128.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/apple-icon-120x120.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/apple-icon-152x152.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/apple-icon-167x167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/apple-icon-167x167.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/apple-icon-180x180.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/favicon-16x16.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/favicon-96x96.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/favicon.ico
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/icon-128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/icon-128x128.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/icon-192x192.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/icon-256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/icon-256x256.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/icon-384x384.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/icon-384x384.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/icon-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/icon-512x512.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mesqueeb/quasar-ui-easy-forms/853699b22091d184103e8682fe81d56a41c008e9/ui/dev/src/statics/icons/ms-icon-144x144.png
--------------------------------------------------------------------------------
/ui/dev/src/statics/icons/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "quasar-ui-easy-forms",
3 | "version": "2.4.2",
4 | "author": "Luca Ban - Mesqueeb",
5 | "description": "A Vue plugin (that uses Quasar components) to easily generate forms by only defining a \"schema\" object.",
6 | "license": "MIT",
7 | "module": "dist/index.esm.js",
8 | "main": "dist/index.common.js",
9 | "scripts": {
10 | "deploy": "npm run build && cd dev && npm run deploy && cd .. && npm publish -otp $OTP && cd ../app-extension && npm publish -otp $OTP",
11 | "deploy-only-lib": "npm run build && npm publish",
12 | "dev": "cd dev && yarn dev && cd ..",
13 | "dev:umd": "yarn build && node build/script.open-umd.js",
14 | "dev:ssr": "cd dev && yarn 'dev:ssr' && cd ..",
15 | "dev:ios": "cd dev && yarn 'dev:ios' && cd ..",
16 | "dev:android": "cd dev && yarn 'dev:android' && cd ..",
17 | "dev:electron": "cd dev && yarn 'dev:electron' && cd ..",
18 | "build": "node build/index.js",
19 | "build:js": "node build/script.javascript.js",
20 | "build:css": "node build/script.css.js",
21 | "test": "ava"
22 | },
23 | "repository": {
24 | "type": "git",
25 | "url": "https://github.com/mesqueeb/quasar-ui-easy-forms"
26 | },
27 | "bugs": "https://github.com/mesqueeb/quasar-ui-easy-forms/issues",
28 | "homepage": "https://quasar-easy-forms.web.app",
29 | "devDependencies": {
30 | "@babel/register": "^7.9.0",
31 | "@rollup/plugin-buble": "^0.21.3",
32 | "@rollup/plugin-commonjs": "^11.1.0",
33 | "@rollup/plugin-json": "^4.0.3",
34 | "@rollup/plugin-node-resolve": "^6.1.0",
35 | "autoprefixer": "^9.8.0",
36 | "ava": "^2.4.0",
37 | "chalk": "^2.4.2",
38 | "cssnano": "^4.1.10",
39 | "esm": "^3.2.25",
40 | "node-sass": "^4.14.1",
41 | "open": "^6.4.0",
42 | "postcss": "^7.0.31",
43 | "postcss-rtl": "^1.7.3",
44 | "quasar": "^1.11.3",
45 | "rimraf": "^3.0.2",
46 | "rollup": "^1.32.1",
47 | "rollup-plugin-vue": "^5.1.9",
48 | "uglify-es": "^3.3.9",
49 | "vue": "^2.6.11",
50 | "vue-template-compiler": "^2.6.11",
51 | "zlib": "^1.0.5"
52 | },
53 | "browserslist": [
54 | "last 1 version, not dead, ie >= 11"
55 | ],
56 | "dependencies": {
57 | "@vue/composition-api": "^0.3.4",
58 | "case-anything": "0.0.1",
59 | "commafy-anything": "^1.1.3",
60 | "copy-anything": "^1.6.0",
61 | "flatten-anything": "^1.4.1",
62 | "is-what": "^3.8.0",
63 | "merge-anything": "^2.4.4",
64 | "nestify-anything": "0.0.0",
65 | "snarkdown": "github:mesqueeb/snarkdown#dist"
66 | },
67 | "ava": {
68 | "require": [
69 | "@babel/register",
70 | "esm"
71 | ],
72 | "helpers": [
73 | "**/helpers/**/*"
74 | ]
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/ui/src/components/EasyForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 | {{ formErrorMsg }}
9 |
10 |
fieldInput({ id: field.id, value, origin })"
16 | />
17 |
18 |
22 | fieldInput({ id: field.id, value, origin })"
28 | :style="
29 | field.span ? `grid-column: ${field.span === true ? '1 / -1' : `span ${field.span}`}` : ''
30 | "
31 | />
32 |
33 |
34 |
35 |
36 |
79 |
80 |
484 |
--------------------------------------------------------------------------------
/ui/src/components/fields/EfBtn.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
54 |
--------------------------------------------------------------------------------
/ui/src/components/fields/EfDiv.vue:
--------------------------------------------------------------------------------
1 |
2 | {{ cValue }}
3 |
4 |
5 |
9 |
10 |
63 |
--------------------------------------------------------------------------------
/ui/src/components/fields/EfMiniForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
36 |
37 |
38 |
50 |
51 |
183 |
--------------------------------------------------------------------------------
/ui/src/components/fields/sharedProps.js:
--------------------------------------------------------------------------------
1 | export const getUsageDocs = fieldTag => `### Usage
2 |
3 | ${fieldTag} is a component that's registered for you; alongside EasyForm and EasyField.
4 | You can use it like:
5 | - \`<${fieldTag} />\` as standalone
6 | - \`\` inside a field (with label & sublabel)
7 | - in an EasyForm "schema" like so: \`component: '${fieldTag}'\``
8 |
--------------------------------------------------------------------------------
/ui/src/components/sharedProps.js:
--------------------------------------------------------------------------------
1 | // prop categories:
2 | // behavior
3 | // content
4 | // general
5 | // model
6 | // state
7 | // style
8 |
9 | const propOf = `This prop can be set on an EasyField or on an EasyForm to make it global.`
10 |
11 | // props both on EasyForm and EasyField
12 | export const mode = {
13 | category: 'state',
14 | type: String,
15 | default: 'edit',
16 | validator: prop => ['edit', 'add', 'view', 'raw'].includes(prop),
17 | values: ['edit', 'add', 'view', 'raw'],
18 | examples: [`'edit'`, `'add'`, `'view'`, `'raw'`],
19 | desc: `The mode represents how fields are rendered
20 | - "edit" or "add" means they can be interacted with
21 | - "view" means they can't
22 | - "raw" means the fields are not generated, just the raw value inside a div
23 |
24 | ${propOf}`,
25 | }
26 | export const labelPosition = {
27 | category: 'style',
28 | type: [String, Function],
29 | default: 'top',
30 | desc: `The position of the label in comparison to the field. ${propOf}`,
31 | values: ['top', 'left'],
32 | examples: ['top', 'left'],
33 | }
34 | export const evaluatedProps = {
35 | category: 'behavior',
36 | type: Array,
37 | default: () => [
38 | 'component',
39 | 'showCondition',
40 | 'label',
41 | 'subLabel',
42 | 'required',
43 | 'rules',
44 | 'fieldStyle',
45 | 'fieldClasses',
46 | 'componentStyle',
47 | 'componentClasses',
48 | 'disable',
49 | 'events',
50 | 'lang',
51 | ],
52 | desc: `An array with prop names that should be treated as "Evaluated Props" when passed a function. ${propOf}`,
53 | }
54 | export const internalLabels = {
55 | category: 'style',
56 | type: [Boolean, undefined],
57 | required: false,
58 | default: undefined,
59 | desc: `Set to true if the component has its own labels and you do not want the EasyField to show a label.
60 |
61 | When \`true\` subLabels will be passed as a prop called 'hint'.
62 |
63 | ${propOf}`,
64 | }
65 | export const internalErrors = {
66 | category: 'behavior|content',
67 | type: [Boolean, undefined],
68 | required: false,
69 | default: undefined,
70 | desc: `Set to true if the component has its own error handling. This makes sure it passes on props like \`rules\` and does nothing with them in the EasyField.
71 |
72 | Will default to \`true\` for components with these names: QInput, QSelect, EfInput, EfSelect, EfInputDate.
73 |
74 | ${propOf}`,
75 | }
76 |
--------------------------------------------------------------------------------
/ui/src/helpers/dateHelpers.js:
--------------------------------------------------------------------------------
1 | import { isString, isDate } from 'is-what'
2 | import { date } from 'quasar'
3 | const { formatDate, adjustDate } = date
4 |
5 | export function dateStamp (date, format) {
6 | if (isString(date)) date = new Date(date)
7 | if (!isDate(date)) return 'null'
8 | if (format === 'short') return formatDate(date, 'YYYY/MM/DD')
9 | if (isString(format)) return formatDate(date, format)
10 | return formatDate(date, 'YYYY年MM月DD日')
11 | }
12 |
13 | export function timeStamp (date) {
14 | if (isString(date)) date = new Date(date)
15 | if (!isDate(date)) return '00:00'
16 | return formatDate(date, 'HH:mm')
17 | }
18 |
19 | export function dateTimeStamp (date) {
20 | return dateStamp(date) + ' ' + timeStamp(date)
21 | }
22 |
23 | /**
24 | * Create a new date object from a dateStamp and timeStamp
25 | *
26 | * @export
27 | * @param {(string|date)} dateStamp eg. 2019-12-31 - a date string or date object
28 | * @param {string} timeStamp eg. 23:59 time in the 24h format
29 | * @returns {date}
30 | */
31 | export function makeDateFromStamps (dateStamp, timeStamp) {
32 | let date = dateStamp
33 | if (isString(date)) date = new Date(date)
34 | if (!isDate(date)) throw new Error('invalid date parameter')
35 | if (!isString(timeStamp)) return date
36 | const hours = Number(timeStamp.split(':')[0])
37 | const minutes = Number(timeStamp.split(':')[1])
38 | return adjustDate(date, { hours, minutes })
39 | }
40 |
41 | export function numberToHourTimestamp (number) {
42 | if (number < 0) {
43 | number = 24 + number
44 | }
45 | const numberAsString = number < 10 ? `0${String(number)}` : String(number)
46 | return `${numberAsString}:00`
47 | }
48 |
--------------------------------------------------------------------------------
/ui/src/helpers/flattenPerSchema.js:
--------------------------------------------------------------------------------
1 | import { flattenObjectProps } from 'flatten-anything'
2 | import { isArray } from 'is-what'
3 |
4 | /**
5 | * Flattens an object to be in line with a schema.
6 | *
7 | * @export
8 | * @param {Object} target the target object
9 | * @param {(Object|Object[])} schema
10 | * @returns {Object}
11 | */
12 | export default function flattenPerSchema (target, schema) {
13 | const schemaArray = isArray(schema) ? schema : Object.values(schema)
14 | const schemaNestedIds = schemaArray
15 | .map(blueprint => blueprint.id)
16 | .filter(id => id && id.includes('.'))
17 | return flattenObjectProps(target, schemaNestedIds)
18 | }
19 |
--------------------------------------------------------------------------------
/ui/src/helpers/focusIfInputEl.js:
--------------------------------------------------------------------------------
1 | export default function focusIfInputEl (e) {
2 | if (!e || !e.srcElement) return
3 | if (e.srcElement.nodeName === 'INPUT') e.srcElement.focus()
4 | if (e.srcElement.nodeName === 'TEXTAREA') e.srcElement.focus()
5 | }
6 |
--------------------------------------------------------------------------------
/ui/src/helpers/parseFieldValue.js:
--------------------------------------------------------------------------------
1 | import { isDate, isNumber, isPlainObject, isArray } from 'is-what'
2 | import commafy from 'commafy-anything'
3 | import { dateStamp } from './dateHelpers'
4 |
5 | /**
6 | * takes a value and returns the parsed value based on an EasyField blueprint provided.
7 | *
8 | * @export
9 | * @param {*} value any value. In our example blueprint `1` should be returned as `'one'`
10 | * @param {Object} blueprint a blueprint like eg.
11 | * `{options: [{value: 1, label: 'one'}]}` out of which the "label" will be retrieved.
12 | * Besides `options` you can also have `prefix` and `suffix`.
13 | * When `valueType: 'date'` it will be printed as short date.
14 | * When `valueType: 'number'` it will receive thousand separators.
15 | * @returns {*} the parsed value
16 | */
17 | export default function parseFieldValue (value, blueprint) {
18 | if (!blueprint) return value
19 | const { valueType, dateFormat, options, multiple, suffix, prefix } = blueprint
20 | let newValue = value
21 | if (isArray(options)) {
22 | if (valueType === 'object' && isPlainObject(value)) {
23 | newValue = multiple
24 | ? Object.values(value)
25 | .filter(v => v)
26 | .join(', ')
27 | : value.label
28 | } else {
29 | const valueArray = !isArray(value) ? [value] : value
30 | newValue = valueArray
31 | .map(selectedValue => {
32 | if (isPlainObject(selectedValue)) return selectedValue.label
33 | const option = options.find(o => o.value === selectedValue) || {}
34 | return option.label || selectedValue
35 | })
36 | .join(', ')
37 | }
38 | }
39 | if (valueType === 'date' && isDate(value)) newValue = dateStamp(newValue, dateFormat)
40 | if (valueType === 'number' && isNumber(value)) newValue = commafy(newValue)
41 | if (suffix) newValue = `${newValue} ${suffix}`
42 | if (prefix) newValue = `${prefix} ${newValue}`
43 | return newValue
44 | }
45 |
--------------------------------------------------------------------------------
/ui/src/helpers/validation.js:
--------------------------------------------------------------------------------
1 | import flattenPerSchema from './flattenPerSchema'
2 | import { isArray, isFunction } from 'is-what'
3 | import defaultLang from '../meta/lang'
4 |
5 | export function createRequiredRule (requiredFieldErrorMsg) {
6 | return val => val === 0 || !!val || requiredFieldErrorMsg
7 | }
8 |
9 | /**
10 | * Validates a field data based on its blueprint
11 | *
12 | * @export
13 | * @param {*} payload
14 | * @param {Blueprint} { rules = [], required }
15 | * @param {Context} context
16 | * @returns {ValidationResultField}
17 | */
18 | export function validateFieldPerSchema (payload, { rules = [], required }, context = {}) {
19 | const lang = context.lang || defaultLang
20 | const rulesEvaluated = !isFunction(rules) ? rules : rules(payload, context)
21 | const requiredEvaluated = !isFunction(required) ? required : required(payload, context)
22 | const requiredRule = createRequiredRule(lang.requiredField)
23 | const rulesToTest = !requiredEvaluated ? rulesEvaluated : [requiredRule, ...rulesEvaluated]
24 | const results = rulesToTest.reduce((carry, rule) => {
25 | carry.push(rule(payload))
26 | return carry
27 | }, [])
28 | const hasAnError = Object.values(results).some(result => result !== true)
29 | return !hasAnError || results
30 | }
31 |
32 | /**
33 | * Validates a form data based on its schema
34 | *
35 | * @export
36 | * @param {PlainObject} formData the form data in an object that looks like: `{[fieldId: string]: any}`
37 | * @param {Schema} schema
38 | * @param {StringObject} lang the lang object with at least the key `requiredField` used as error message for required fields
39 | * @returns {ValidationResultForm}
40 | */
41 | export function validateFormPerSchema (formData, schema, lang) {
42 | const schemaObject = !isArray(schema)
43 | ? schema
44 | : schema.reduce((carry, blueprint) => {
45 | carry[blueprint.id] = blueprint
46 | return carry
47 | }, {})
48 | const formDataFlatEmpty = Object.keys(schemaObject)
49 | .reduce((carry, key) => ({ ...carry, [key]: null }), {}) // prettier-ignore
50 | const formDataFlatCurrent = flattenPerSchema(formData, schema)
51 | const formDataFlat = { ...formDataFlatEmpty, ...formDataFlatCurrent }
52 | const resultPerField = Object.entries(formDataFlat).reduce((carry, [fieldId, fieldValue]) => {
53 | const blueprint = schemaObject[fieldId]
54 | const context = { formData, formDataFlat, lang }
55 | carry[fieldId] = !blueprint || validateFieldPerSchema(fieldValue, blueprint, context)
56 | return carry
57 | }, {})
58 | return resultPerField
59 | }
60 |
--------------------------------------------------------------------------------
/ui/src/helpers/validation.ts:
--------------------------------------------------------------------------------
1 | import { isArray } from 'is-what'
2 | import { PlainObject, Blueprint, Schema, StringObject } from '../types'
3 | import flattenPerSchema from './flattenPerSchema'
4 |
5 | export type ValidationResultField = boolean | (string | boolean)[]
6 | export type ValidationResultForm = { [fieldId: string]: ValidationResultField }
7 |
8 | /**
9 | * Validates a field data based on its blueprint
10 | *
11 | * @export
12 | * @param {Blueprint} { rules = [], required }
13 | * @param {*} payload
14 | * @param {StringObject} lang
15 | * @returns {ValidationResultField}
16 | */
17 | export function validateFieldPerSchema (
18 | { rules = [], required }: Blueprint,
19 | payload: any,
20 | lang: StringObject
21 | ): ValidationResultField {
22 | const requiredRule = (val: any) => val === 0 || !!val || lang.requiredField
23 | const testRules = !required ? rules : [requiredRule, ...rules]
24 | const results = testRules.reduce((carry, rule) => {
25 | carry.push(rule(payload))
26 | return carry
27 | }, [])
28 | const hasAnError = Object.values(results).some(result => result !== true)
29 | return !hasAnError || results
30 | }
31 |
32 | /**
33 | * Validates a form data based on its schema
34 | *
35 | * @export
36 | * @param {PlainObject} formData the flattened form data in an object that looks like: `{[fieldId: string]: any}`
37 | * @param {Schema} schema
38 | * @param {StringObject} lang the lang object with at least the key `requiredField` used as error message for required fields
39 | * @returns {ValidationResultForm}
40 | */
41 | export function validateFormPerSchema (
42 | formData: PlainObject,
43 | schema: Schema,
44 | lang: StringObject
45 | ): ValidationResultForm {
46 | const schemaObject = !isArray(schema)
47 | ? schema
48 | : schema.reduce((carry, blueprint) => {
49 | carry[blueprint.id] = blueprint
50 | return carry
51 | }, {})
52 | const formDataFlatEmpty = Object.keys(schemaObject)
53 | .reduce((carry, key) => ({ ...carry, [key]: null }), {}) // prettier-ignore
54 | const formDataFlatCurrent = flattenPerSchema(formData, schema)
55 | const formDataFlat = { ...formDataFlatEmpty, ...formDataFlatCurrent }
56 | const resultPerField = Object.entries(formDataFlat).reduce((carry, [fieldId, fieldValue]) => {
57 | const blueprint = schemaObject[fieldId]
58 | carry[fieldId] = validateFieldPerSchema(blueprint, fieldValue, lang)
59 | return carry
60 | }, {})
61 | return resultPerField
62 | }
63 |
--------------------------------------------------------------------------------
/ui/src/index.js:
--------------------------------------------------------------------------------
1 | import { version } from '../package.json'
2 |
3 | import EasyForm from './components/EasyForm.vue'
4 | import EasyField from './components/EasyField.vue'
5 |
6 | import EfBtn from './components/fields/EfBtn.vue'
7 | import EfDiv from './components/fields/EfDiv.vue'
8 | import EfMiniForm from './components/fields/EfMiniForm.vue'
9 |
10 | import dependencyMap from './meta/dependencyMap'
11 | import { validateFormPerSchema } from './helpers/validation.js'
12 |
13 | export {
14 | version,
15 | EasyForm,
16 | EasyField,
17 | EfBtn,
18 | EfDiv,
19 | EfMiniForm,
20 | dependencyMap,
21 | validateFormPerSchema,
22 | }
23 |
24 | export default {
25 | version,
26 |
27 | EasyForm,
28 | EasyField,
29 |
30 | EfBtn,
31 | EfDiv,
32 | EfMiniForm,
33 |
34 | dependencyMap,
35 | validateFormPerSchema,
36 |
37 | install (Vue) {
38 | Vue.component(EasyField.name, EasyField)
39 | Vue.component(EasyForm.name, EasyForm)
40 |
41 | Vue.component(EfBtn.name, EfBtn)
42 | Vue.component(EfDiv.name, EfDiv)
43 | Vue.component(EfMiniForm.name, EfMiniForm)
44 | },
45 | }
46 |
--------------------------------------------------------------------------------
/ui/src/index.sass:
--------------------------------------------------------------------------------
1 | // when installed as package needs to be:
2 | // @ import 'quasar/src/css/variables.sass'
3 | // when using npm run dev in `dev` folder it needs to be:
4 | // @ import '../node_modules/quasar/src/css/variables.sass'
5 |
6 | @import 'margin-padding.sass'
7 |
--------------------------------------------------------------------------------
/ui/src/margin-padding.sass:
--------------------------------------------------------------------------------
1 |
2 | $_space-base: 16px
3 |
4 | $xxs: ($_space-base * .1)
5 | $xs: ($_space-base * .25)
6 | $sm: ($_space-base * .5)
7 | $md: $_space-base
8 | $lg: ($_space-base * 1.5)
9 | $xl: ($_space-base * 2.3)
10 | $xxl: ($_space-base * 3)
11 | $xxxl: ($_space-base * 5)
12 |
--------------------------------------------------------------------------------
/ui/src/meta/dependencyMap.js:
--------------------------------------------------------------------------------
1 | import merge from 'merge-anything'
2 | // import { QBtn, QInput } from './quasarPropsJson'
3 |
4 | export const dependencyMap = {
5 | QInput: {
6 | componentName: 'QInput',
7 | component: 'QInput',
8 | passedProps: {},
9 | },
10 | EfBtn: {
11 | componentName: 'EfBtn',
12 | component: 'EfBtn',
13 | passedProps: {},
14 | },
15 | EfDiv: {
16 | componentName: 'EfDiv',
17 | component: 'EfDiv',
18 | passedProps: {},
19 | },
20 | EfMiniForm: {
21 | componentName: 'EfMiniForm',
22 | component: 'EfMiniForm',
23 | passedProps: {},
24 | },
25 | }
26 |
27 | export function getPassedProps (component) {
28 | const info = dependencyMap[component] || {}
29 | const { passedProps } = info
30 | return merge(...Object.values(passedProps))
31 | }
32 |
33 | export default { ...dependencyMap, getPassedProps }
34 |
--------------------------------------------------------------------------------
/ui/src/meta/lang.js:
--------------------------------------------------------------------------------
1 | const defaultLang = {
2 | archive: 'Archive',
3 | delete: 'Delete',
4 | cancel: 'Cancel',
5 | edit: 'Edit',
6 | save: 'Save',
7 | requiredField: 'Field is required',
8 | formValidationError: 'There are remaining errors.',
9 | }
10 |
11 | export default defaultLang
12 |
--------------------------------------------------------------------------------
/ui/src/meta/lang.ts:
--------------------------------------------------------------------------------
1 | import { StringObject } from '../types'
2 |
3 | const defaultLang: StringObject = {
4 | archive: 'Archive',
5 | delete: 'Delete',
6 | cancel: 'Cancel',
7 | edit: 'Edit',
8 | save: 'Save',
9 | requiredField: 'Field is required',
10 | formValidationError: 'There are remaining errors.',
11 | }
12 |
13 | export default defaultLang
14 |
--------------------------------------------------------------------------------
/ui/src/meta/quasarPropsJson.js:
--------------------------------------------------------------------------------
1 | import copy from 'copy-anything'
2 | import { camelCase } from 'case-anything'
3 | import { isArray } from 'is-what'
4 | import QBtnJson from 'quasar/dist/api/QBtn.json'
5 | import QBtnToggleJson from 'quasar/dist/api/QBtnToggle.json'
6 | import QImgJson from 'quasar/dist/api/QImg.json'
7 | import QInputJson from 'quasar/dist/api/QInput.json'
8 | import QRangeJson from 'quasar/dist/api/QRange.json'
9 | import QSelectJson from 'quasar/dist/api/QSelect.json'
10 | import QSliderJson from 'quasar/dist/api/QSlider.json'
11 | import QToggleJson from 'quasar/dist/api/QToggle.json'
12 | import QUploaderJson from 'quasar/dist/api/QUploader.json'
13 | import QVideoJson from 'quasar/dist/api/QVideo.json'
14 |
15 | const stringToTypeFnDictionary = {
16 | Object: Object,
17 | Function: Function,
18 | Array: Array,
19 | String: String,
20 | Number: Number,
21 | Boolean: Boolean,
22 | RegExp: RegExp,
23 | Date: Date,
24 | Symbol: Symbol,
25 | }
26 | const stringToTypeFn = string => stringToTypeFnDictionary[string]
27 |
28 | function jsonToPropFormat ({ props }) {
29 | if (!props) return {}
30 | return Object.entries(props).reduce((carry, [key, value]) => {
31 | value = copy(value)
32 | value.inheritedProp = true
33 | value.type = isArray(value.type) ? value.type.map(stringToTypeFn) : stringToTypeFn(value.type)
34 | carry[camelCase(key)] = value
35 | return carry
36 | }, {})
37 | }
38 |
39 | export const QBtn = jsonToPropFormat(QBtnJson)
40 | export const QBtnToggle = jsonToPropFormat(QBtnToggleJson)
41 | export const QImg = jsonToPropFormat(QImgJson)
42 | export const QInput = jsonToPropFormat(QInputJson)
43 | export const QRange = jsonToPropFormat(QRangeJson)
44 | export const QSelect = jsonToPropFormat(QSelectJson)
45 | export const QSlider = jsonToPropFormat(QSliderJson)
46 | export const QToggle = jsonToPropFormat(QToggleJson)
47 | export const QUploader = jsonToPropFormat(QUploaderJson)
48 | export const QVideo = jsonToPropFormat(QVideoJson)
49 |
50 | export default {
51 | QBtn,
52 | QBtnToggle,
53 | QImg,
54 | QInput,
55 | QRange,
56 | QSelect,
57 | QSlider,
58 | QToggle,
59 | QUploader,
60 | QVideo,
61 | }
62 |
--------------------------------------------------------------------------------
/ui/src/types/index.ts:
--------------------------------------------------------------------------------
1 | export type PlainObject = { [key: string]: any }
2 | export type StringObject = { [key: string]: string }
3 | export type Schema = Blueprint[] | { [key: string]: Blueprint }
4 |
5 | export interface Blueprint {
6 | rules?: ((val: any) => boolean | string)[]
7 | required?: boolean
8 | [key: string]: any
9 | }
10 |
--------------------------------------------------------------------------------
/ui/test/flattenPerSchema.js:
--------------------------------------------------------------------------------
1 | import test from 'ava'
2 | import flattenPerSchema from '../src/helpers/flattenPerSchema.js'
3 |
4 | test('flattenPerSchema', t => {
5 | let res, target, schema
6 | target = {
7 | payAsYouGoPrice: [],
8 | price: 0,
9 | timeFee: { d: 23, m: 0, n: 12.97 },
10 | timeFeeBreakdown: {
11 | d: { from: 8, to: 22 },
12 | e: { from: 21, to: 21 },
13 | m: { from: 9, to: 9 },
14 | n: { from: -2, to: 8 },
15 | },
16 | }
17 | schema = {
18 | 'payAsYouGoPrice': {
19 | id: 'payAsYouGoPrice',
20 | label: '使用料別電気単価',
21 | valueType: 'array',
22 | schema: [
23 | {
24 | id: 'to',
25 | label: '電気量範囲',
26 | valueType: 'number',
27 | prefix: 'kWh以上~',
28 | suffix: 'kWh未満',
29 | },
30 | {
31 | id: 'price',
32 | label: '電気単価',
33 | valueType: 'number',
34 | suffix: '円/kWh',
35 | },
36 | ],
37 | showCondition: [['planType', '===', 'payAsYouGo']],
38 | },
39 | 'timeFee.d': {
40 | id: 'timeFee.d',
41 | label: '昼間の電気単価',
42 | valueType: 'number',
43 | suffix: '円/kW',
44 | showCondition: [['planType', '===', 'allElecPlan']],
45 | },
46 | 'timeFeeBreakdown.d': {
47 | id: 'timeFeeBreakdown.d',
48 | label: '昼間の適用時間帯',
49 | valueType: 'object',
50 | showCondition: [['planType', '===', 'allElecPlan']],
51 | min: 0,
52 | max: 24,
53 | },
54 | 'timeFee.n': {
55 | id: 'timeFee.n',
56 | label: '夜間の電気単価',
57 | valueType: 'number',
58 | suffix: '円/kW',
59 | showCondition: [['planType', '===', 'allElecPlan']],
60 | },
61 | 'timeFeeBreakdown.n': {
62 | id: 'timeFeeBreakdown.n',
63 | label: '夜間の適用時間帯',
64 | valueType: 'object',
65 | showCondition: [['planType', '===', 'allElecPlan']],
66 | min: -12,
67 | max: 12,
68 | },
69 | 'hasMorningFee': {
70 | id: 'hasMorningFee',
71 | label: '朝夕間の電気料金の有無',
72 | valueType: 'boolean',
73 | showCondition: [['planType', '===', 'allElecPlan']],
74 | },
75 | 'timeFeeBreakdown.m': {
76 | id: 'timeFeeBreakdown.m',
77 | label: '朝の適用時間帯',
78 | valueType: 'object',
79 | showCondition: [
80 | ['planType', '===', 'allElecPlan'],
81 | ['hasMorningFee', '===', true],
82 | ],
83 | min: 0,
84 | max: 24,
85 | },
86 | 'timeFee.m': {
87 | id: 'timeFee.m',
88 | label: '朝夕間の電気単価',
89 | valueType: 'number',
90 | suffix: '円/kW',
91 | showCondition: [
92 | ['planType', '===', 'allElecPlan'],
93 | ['hasMorningFee', '===', true],
94 | ],
95 | },
96 | 'timeFeeBreakdown.e': {
97 | id: 'timeFeeBreakdown.e',
98 | label: '夕方の適用時間帯',
99 | valueType: 'object',
100 | showCondition: [
101 | ['planType', '===', 'allElecPlan'],
102 | ['hasMorningFee', '===', true],
103 | ],
104 | min: 0,
105 | max: 24,
106 | },
107 | }
108 | res = flattenPerSchema(target, schema)
109 | t.deepEqual(res, {
110 | 'payAsYouGoPrice': [],
111 | 'price': 0,
112 | 'timeFee': {},
113 | 'timeFee.d': 23,
114 | 'timeFee.m': 0,
115 | 'timeFee.n': 12.97,
116 | 'timeFeeBreakdown': {},
117 | 'timeFeeBreakdown.d': { from: 8, to: 22 },
118 | 'timeFeeBreakdown.e': { from: 21, to: 21 },
119 | 'timeFeeBreakdown.m': { from: 9, to: 9 },
120 | 'timeFeeBreakdown.n': { from: -2, to: 8 },
121 | })
122 | })
123 |
--------------------------------------------------------------------------------
/ui/test/index.js:
--------------------------------------------------------------------------------
1 | import test from 'ava'
2 |
3 | test('t', t => {
4 | t.pass()
5 | })
6 |
--------------------------------------------------------------------------------
/ui/test/parseFieldValue.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-unused-vars */
2 | /* eslint-disable prefer-const */
3 | import test from 'ava'
4 | import { flattenObject } from 'flatten-anything'
5 | import nestify from 'nestify-anything'
6 | import parseFieldValue from '../src/helpers/parseFieldValue.js'
7 |
8 | const parseObject = (target, schema) => {
9 | const flat = flattenObject(target)
10 | const parsed = Object.entries(flat).reduce((carry, [key, value]) => {
11 | const blueprint = schema.find(bp => bp.id === key) || {}
12 | carry[key] = parseFieldValue(value, blueprint)
13 | return carry
14 | }, {})
15 | return nestify(parsed)
16 | }
17 |
18 | test('options', t => {
19 | let res, target, schema
20 | target = { chargeCycle: 2 }
21 | schema = [
22 | {
23 | id: 'chargeCycle',
24 | label: '充電サイクル',
25 | valueType: 'number',
26 | options: [
27 | { label: '1回', value: 1 },
28 | { label: '2回', value: 2 },
29 | ],
30 | },
31 | ]
32 | res = parseObject(target, schema)
33 | t.deepEqual(res, { chargeCycle: '2回' })
34 | })
35 |
36 | test('suffix', t => {
37 | let res, target, schema
38 | target = { batteryCapacity: 1 }
39 | schema = [
40 | {
41 | id: 'batteryCapacity',
42 | label: '定格容量',
43 | valueType: 'number',
44 | suffix: 'kWh',
45 | },
46 | ]
47 | res = parseObject(target, schema)
48 | t.deepEqual(res, { batteryCapacity: '1kWh' })
49 | })
50 |
51 | test('prefix', t => {
52 | let res, target, schema
53 | target = { price: 1 }
54 | schema = [
55 | {
56 | id: 'price',
57 | valueType: 'number',
58 | prefix: '¥',
59 | },
60 | ]
61 | res = parseObject(target, schema)
62 | t.deepEqual(res, { price: '¥1' })
63 | })
64 |
65 | test('nested props', t => {
66 | let res, target, schema
67 | target = { sizes: { d: 1, h: 2 } }
68 | schema = [
69 | {
70 | id: 'sizes.d',
71 | suffix: 'm',
72 | },
73 | ]
74 | res = parseObject(target, schema)
75 | t.deepEqual(res, { sizes: { d: '1m', h: 2 } })
76 | })
77 |
78 | test('options, suffix & prefix', t => {
79 | let res, target, schema
80 | target = { chargeCycle: 2 }
81 | schema = [
82 | {
83 | id: 'chargeCycle',
84 | options: [
85 | { label: '1回', value: 1 },
86 | { label: '2回', value: 2 },
87 | ],
88 | prefix: 'a',
89 | suffix: 'z',
90 | },
91 | ]
92 | res = parseObject(target, schema)
93 | t.deepEqual(res, { chargeCycle: 'a2回z' })
94 | })
95 |
--------------------------------------------------------------------------------
/ui/umd-test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | UMD test
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | quasar-ui-easy-forms v{{ version }}
22 |
23 |
24 | Quasar v{{ $q.version }}
25 |
26 |
27 |
28 |
29 |
30 |
31 | - In /ui, run: "yarn build"
32 | - You need to build & refresh page on each change manually.
33 | - Use self-closing tags only!
34 | - Example: <my-component></my-component>
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
57 |
58 |
59 |
--------------------------------------------------------------------------------