Просмотр исходного кода

Feature: Mobile - Add "required" attribute to Form schema

Vladimir Sheremet 2 лет назад
Родитель
Сommit
5a176e1194

+ 1 - 1
app/frontend/apps/mobile/form/theme/global/getCoreClasses.ts

@@ -12,7 +12,7 @@ export const addFloatingLabel = (classes: Classes): Classes => {
     wrapper: `${classes.wrapper || ''} relative`,
     inner: 'flex',
     input: `${inputClass} w-full h-14 text-sm bg-gray-500 rounded-xl border-none focus:outline-none placeholder:text-transparent focus-within:pt-8 formkit-populated:pt-8`,
-    label: `${labelClass} absolute top-0 left-0 py-5 px-3 h-14 text-base transition-all duration-100 ease-in-out origin-left pointer-events-none formkit-populated:-translate-y-3 formkit-populated:translate-x-1 formkit-populated:scale-75 formkit-populated:opacity-75`,
+    label: `${labelClass} absolute top-0 left-0 py-5 px-3 h-14 text-base transition-all duration-100 ease-in-out origin-left pointer-events-none formkit-populated:-translate-y-3 formkit-populated:translate-x-1 formkit-populated:scale-75 formkit-populated:opacity-75 formkit-required:required`,
   }
 }
 

+ 1 - 0
app/frontend/shared/components/Form/types.ts

@@ -39,6 +39,7 @@ export interface FormSchemaField {
   placeholder?: string
   help?: string
   disabled?: boolean
+  required?: boolean
   delay?: number
   errors?: string[]
   id?: string

+ 0 - 1
app/frontend/shared/form/features/addLink.ts

@@ -51,7 +51,6 @@ const addLink = (settings?: AddLinkExtensionOptions) => (node: FormKitNode) => {
         ],
       },
     }
-    console.log(extensions, localExtensions)
     return originalSchema(localExtensions)
   }
 

+ 15 - 5
app/frontend/shared/form/plugins/__tests__/addValuePopulatedDataAttribute.spec.ts → app/frontend/shared/form/plugins/__tests__/extendDataAttributes.spec.ts

@@ -7,14 +7,14 @@ import {
 } from '@formkit/core'
 import { FormKit } from '@formkit/vue'
 import { renderComponent } from '@tests/support/components'
-import addValuePopulatedDataAttribute from '../global/addValuePopulatedDataAttribute'
+import extendDataAttribues from '../global/extendDataAttributes'
 
 const wrapperParameters = {
   form: true,
   formField: true,
 }
 
-const renderKit = () => {
+const renderKit = (props: any = {}) => {
   const kit = renderComponent(FormKit, {
     ...wrapperParameters,
     props: {
@@ -22,6 +22,7 @@ const renderKit = () => {
       type: 'text',
       id: 'text',
       label: 'text',
+      ...props,
     },
   })
   return {
@@ -30,7 +31,7 @@ const renderKit = () => {
   }
 }
 
-describe('addValuePopulatedDataAttribute', () => {
+describe('extendDataAttributes - data-populated', () => {
   describe('renders on output', () => {
     const originalSchema = vi.fn()
     const inputNode = createNode({
@@ -50,7 +51,7 @@ describe('addValuePopulatedDataAttribute', () => {
     })
 
     test('applies schema on input', () => {
-      addValuePopulatedDataAttribute(inputNode)
+      extendDataAttribues(inputNode)
 
       const schema = (inputNode.props.definition?.schema ||
         (() => ({}))) as FormKitExtendableSchemaRoot
@@ -63,7 +64,7 @@ describe('addValuePopulatedDataAttribute', () => {
     })
 
     test('skips non-inputs', () => {
-      addValuePopulatedDataAttribute({
+      extendDataAttribues({
         ...inputNode,
         type: 'list',
       })
@@ -89,3 +90,12 @@ describe('addValuePopulatedDataAttribute', () => {
     })
   })
 })
+
+describe('extendDataAttributes - data-required', () => {
+  it('has data-required if field is required', () => {
+    const kit = renderKit({
+      required: true,
+    })
+    expect(kit.getOuterKit()).toHaveAttribute('data-required')
+  })
+})

+ 90 - 0
app/frontend/shared/form/plugins/__tests__/requiredValidation.spec.ts

@@ -0,0 +1,90 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import { createNode, FormKitFrameworkContext } from '@formkit/core'
+import requiredValidation from '../global/requiredValidation'
+
+const createInput = (props: any = {}) => {
+  const originalSchema = vi.fn()
+  const inputNode = createNode({
+    type: 'input',
+    value: 'Test node',
+    props: {
+      definition: { type: 'input', schema: originalSchema },
+      ...props,
+    },
+  })
+
+  inputNode.context = {
+    fns: {},
+  } as FormKitFrameworkContext
+
+  return inputNode
+}
+
+describe('requiredValidation', () => {
+  it('doesnt add validation rule', () => {
+    const inputNode = createInput({})
+
+    requiredValidation(inputNode)
+
+    expect(inputNode.props.validation).not.toBe('required')
+  })
+
+  it('adds validation rule', () => {
+    const inputNode = createInput({
+      required: true,
+    })
+
+    requiredValidation(inputNode)
+
+    expect(inputNode.props.validation).toBe('required')
+  })
+
+  it('appends validation rule to string', () => {
+    const inputNode = createInput({
+      required: true,
+      validation: 'number',
+    })
+
+    requiredValidation(inputNode)
+
+    expect(inputNode.props.validation).toBe('number|required')
+  })
+
+  it('appends validation rule to array', () => {
+    const inputNode = createInput({
+      required: true,
+      validation: [['number']],
+    })
+
+    requiredValidation(inputNode)
+
+    expect(inputNode.props.validation).toEqual([['number'], ['required']])
+  })
+
+  it('required prop removed from input', () => {
+    const inputNode = createInput({
+      required: true,
+      validation: [['number']],
+    })
+
+    requiredValidation(inputNode)
+
+    inputNode.emit('prop:required', false)
+
+    expect(inputNode.props.validation).toEqual([['number']])
+  })
+
+  it('required prop removed from string', () => {
+    const inputNode = createInput({
+      required: true,
+      validation: 'number',
+    })
+
+    requiredValidation(inputNode)
+
+    inputNode.emit('prop:required', false)
+
+    expect(inputNode.props.validation).toBe('number')
+  })
+})

+ 22 - 3
app/frontend/shared/form/plugins/global/addValuePopulatedDataAttribute.ts → app/frontend/shared/form/plugins/global/extendDataAttributes.ts

@@ -3,19 +3,33 @@
 import { cloneDeep, isEmpty } from 'lodash-es'
 import { FormKitNode, FormKitExtendableSchemaRoot } from '@formkit/core'
 
-const addValuePopulatedDataAttribute = (node: FormKitNode) => {
+const extendDataAttribues = (node: FormKitNode) => {
   const { props, context } = node
 
   if (!props.definition || !context || node.type !== 'input') return
 
   // Adds a helper function to check the existing value inside of the context.
-  context.fns.hasValue = (value) => {
+  context.fns.hasValue = (value: unknown): boolean => {
     if (typeof value === 'object') return !isEmpty(value)
     if (typeof value === 'number') return value !== undefined && value !== null
 
     return !!value
   }
 
+  context.fns.hasRule = (
+    rule?: string,
+    ruleSet?: string | Array<Array<string>>,
+  ) => {
+    if (!ruleSet || !rule) return false
+
+    if (Array.isArray(ruleSet)) return ruleSet.some((r) => r.includes(rule))
+
+    return ruleSet
+      .split('|')
+      .map((r) => r.split(/:|,+/g))
+      .some((r) => r.includes(rule))
+  }
+
   const definition = cloneDeep(props.definition)
 
   const originalSchema = definition.schema as FormKitExtendableSchemaRoot
@@ -30,6 +44,11 @@ const addValuePopulatedDataAttribute = (node: FormKitNode) => {
             then: 'true',
             else: undefined,
           },
+          'data-required': {
+            if: '$fns.hasRule("required", $node.props.validation)',
+            then: 'true',
+            else: undefined,
+          },
         },
       },
     }
@@ -39,4 +58,4 @@ const addValuePopulatedDataAttribute = (node: FormKitNode) => {
   props.definition = definition
 }
 
-export default addValuePopulatedDataAttribute
+export default extendDataAttribues

+ 60 - 0
app/frontend/shared/form/plugins/global/requiredValidation.ts

@@ -0,0 +1,60 @@
+// Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
+
+import { FormKitNode } from '@formkit/core'
+
+const addRequiredValidation = (node: FormKitNode) => {
+  node.addProps(['required'])
+
+  const { props } = node
+
+  const addRequired = () => {
+    const { validation } = props
+    if (Array.isArray(validation)) {
+      validation.push(['required'])
+      return
+    }
+
+    if (!validation) {
+      props.validation = 'required'
+      return
+    }
+
+    if (!validation.includes('required')) {
+      props.validation = `${validation}|required`
+    }
+  }
+
+  const removeRequired = () => {
+    const { validation } = props
+
+    if (!validation) {
+      return
+    }
+
+    if (Array.isArray(validation)) {
+      props.validation = validation.filter(([rule]) => rule !== 'required')
+      return
+    }
+
+    if (validation.includes('required')) {
+      props.validation = validation
+        .split('|')
+        .filter((rule: string) => !rule.includes('required'))
+        .join('|')
+    }
+  }
+
+  if (props.required) {
+    addRequired()
+  }
+
+  node.on('prop:required', ({ payload }) => {
+    if (payload) {
+      addRequired()
+    } else {
+      removeRequired()
+    }
+  })
+}
+
+export default addRequiredValidation

+ 7 - 0
app/frontend/shared/styles/main.css

@@ -4,6 +4,13 @@
 
 @tailwind utilities;
 
+@layer components {
+  .required::after {
+    content: '•';
+    @apply ml-1 font-extrabold text-yellow;
+  }
+}
+
 @layer base {
   @font-face {
     font-family: 'Fira Sans';

+ 1 - 1
app/models/form_schema/field.rb

@@ -16,7 +16,7 @@ class FormSchema::Field
   end
 
   # Add base attributes
-  attribute :show, :type, :name, :label, :labelPlaceholder, :value, :help, :id, :disabled, :delay, :errors, :sectionsSchema, :classes, :validation, :validationMessage, :validationVisibility, :outerClass, :wrapperClass, :labelClass, :prefixClass, :innerClass, :suffixClass, :inputClass, :helpClass, :messagesClass, :messageClass
+  attribute :show, :type, :name, :label, :labelPlaceholder, :value, :help, :id, :disabled, :delay, :errors, :sectionsSchema, :classes, :validation, :validationMessage, :validationVisibility, :outerClass, :wrapperClass, :labelClass, :prefixClass, :innerClass, :suffixClass, :inputClass, :helpClass, :messagesClass, :messageClass, :required
 
   # Type of the current field object. By default, this is inferred from the
   #   class name; override if needed.

+ 2 - 2
app/models/form_schema/form/mobile/login.rb

@@ -20,14 +20,14 @@ class FormSchema::Form::Mobile::Login < FormSchema::Form
         name:        'login',
         label:       __('Username / Email'),
         placeholder: __('Username / Email'),
-        validation:  'required',
+        required:    true,
       ).schema,
       FormSchema::Field::Password.new(
         context:     context,
         name:        'password',
         label:       __('Password'),
         placeholder: __('Password'),
-        validation:  'required',
+        required:    true,
       ).schema,
       # *object_attribute_fields,
       {

Некоторые файлы не были показаны из-за большого количества измененных файлов