5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/.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/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/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/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/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/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/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/dev/src/components/Template.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
20 |
--------------------------------------------------------------------------------
/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/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/components/Snarkdown.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
27 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/pages/NewDemo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
30 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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/components/InfoBoxWrapper.vue:
--------------------------------------------------------------------------------
1 |
2 |
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/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/src/components/fields/EfMiniForm.vue:
--------------------------------------------------------------------------------
1 |
2 |