2.1.0 • Published 3 years ago

@ssdcode/vue-form v2.1.0

Weekly downloads
94
License
MIT
Repository
github
Last release
3 years ago

Vue Form Component

Set of components to handle your online form interaction.

You'll find most of the instructions below, but to have a more in depth understanding of how this set of components works, you might want to watch Advanced VueJs Form Component series, which shows how it's been built step by step and explains all design decisions.

Installation

npm i @ssdcode/vue-form --save-dev

Dependencies

This package makes use of the following packages

vue
@ssdcode/cms-partials
@ssdcode/vue-event-bus
@ssdcode/vue-focus-directive

which you will have to import and register

import Vue from 'vue'
import EventBus from '@ssdcode/vue-event-bus';
import { FocusDirective } from '@ssdcode/vue-focus-directive';

window.EventBus = window.EventBus || new EventBus;

import VueForm from '@ssdcode/vue-form';

Lastly you can register it with your vue instance

const app = new Vue({
    el: '#app',
    directives: {
        FocusDirective
    },
    components: {
        // ...
        ...VueForm
    }
});

Styles

Package comes with 2 scss files to style your components, but you can use your own styling if you've decided to use a different structure for form-validation, top-dialog. I recommend to check these files in case you need some of the styling for your particular case.

src/styles/components/_form.scss
src/styles/components/_top-dialog.scss

Main components

Top dialog

Include <top-dialog> just before the closing div with id #app (or whichever one serves as your app's wrapper)

<div id="app">

    // ... page elements
    
    <top-dialog></top-dialog>
    
</div>

Form wrapper

<form-wrapper 
    group="update-form"
    action="/"
    v-slot:default="{
        group,
        fields,
        error,
        isDisabled,
        processing,
        reset,
        clear,
        disableEvent,
        enableEvent
    }"
    v-cloak
>
    //... form inputs and buttons go here
</form-wrapper>

or if you are providing support for older browsers that do not support object destructuring:

<form-wrapper 
    group="update-form"
    action="/"
    v-slot:default="props"
    v-cloak
>
    //... form inputs and buttons go here
</form-wrapper>

Please make sure that in this case you will have to prepend props.* to all properties you'll use within the rest of this documentation i.e. v-model="props.fields.last_name" as opposed to just v-model="fields.last_name"

Input components

The package comes with component for most commonly used input types. Most of them can render validation errors by just passing :validation and :error props. To overwrite the way the validation is rendered, you can pass it between the opening and closing tag of the given input component.

Text input

Please note :group="group" represents group associated with the form-wrapper, hence after destructuring slot scope you can pass it in dynamically

<text-input
    :group="group"
    label="Last name: *"
    name="last_name"
    v-model="fields.last_name"
    maxlength="30"
    autocomplete="family-name"
    :validation="{
        'required': 'Please provide your last name',
        'min:2': 'Minimum length 2 chars.',
        'max:30': 'Maximum length 30 chars.',
    }"
    :error="error"
    :disabled="isDisabled"
></text-input>

Email input

<email-input
    :group="group"
    label="Email: *"
    name="email"
    v-model="fields.email"
    :validation="{
        'required': 'Please provide a valid email address',
        'email': 'Invalid email address'
    }"
    :error="error"
    :disabled="isDisabled"
></email-input>

Password input

<password-input
    :group="group"
    name="password"
    label="Password: *"
    v-model="fields.password"
    autocomplete="off"
    :validation="{
        'required': 'Please provide your password',
        'password': 'Invalid password format',
    }"
    :error="error"
    :disabled="isDisabled"
></password-input>

Other input types

In addition to the three input components above there are also the following available:

date-input
time-input
datetime-input
hidden-input
number-input
float-input

Radio input

Because radio button renders multiple inputs, but expects a single value, validation needs to be rendered outside of the component. When rendering validation outside of the component, you only need to pass the :validation rules (without associated messages) in the form of the array - as shown in the example below.

<form-validation
    id="city"
    name="city"
    :show="error.has('city')"
    :validation="{
        'required': 'Please select your city',
        'in': 'Please select your city',
    }"
    :error="error"
    css-class="block"
></form-validation>

<radio-input
    :group="group"
    name="city"
    :options="[
        {
          name: 'London',
          value: 1,
        },
        {
          name: 'Paris',
          value: 2,
        },
        {
          name: 'Madrid',
          value: 3,
        },
        {
          name: 'Amsterdam',
          value: 6,
        }
    ]"
    v-model="fields.city"
    :validation="['required', 'in:1,2,3,6']"
    :error="error"
    :disabled="isDisabled"
></radio-input>

Single checkbox

You can specify what value should be returned for checked and unchecked status of the checkbox using true-value (when checked) and false-value (otherwise). The current-value property allows you to specify value of the input when the form first loads - allowing you to have it automatically checked if it is the same as true-value.

If you omit true/false-value properties, by default boolean true/false will be used.

<checkbox-input
    :group="group"
    name="share"
    label="Please share my data with third party"
    v-model="fields.share"
    :validation="{
        'required': 'Invalid selection',
        'in:a': 'We have to share your data'
    }"
    :error="error"
    current-value="a"
    true-value="a"
    false-value="b"
    validation-css-class="block"
    :disabled="isDisabled"
></checkbox-input>

If you would like to exclude the unchecked field from the list of inputs when sending the request, use the remove-when-false property and set it to true:

<checkbox-input
    :group="group"
    name="privacy"
    label="I agree with the privacy policy"
    v-model="fields.privacy"
    :validation="{
        'required': 'You have to agree with our privacy policy'
    }"
    :error="error"
    :remove-when-false="true"
    validation-css-class="block"
    :disabled="isDisabled"
></checkbox-input>

Group of checkboxes (produces array)

This input produces array consisting of all checked values i.e. ['blue', 'green', 'orange'].

Again, because it is a multi select, validation is being rendered outside of the input component.

<form-validation
    id="colours"
    name="colours"
    :show="error.has('colours')"
    :validation="{
        'required': 'Please select exactly 2 items',
        'min': 'Please select exactly 2 items',
        'max': 'Please select exactly 2 items'
    }"
    :error="error"
    css-class="block"
></form-validation>
    
<div class="checkbox-group">

    <checkbox-group-input
        :group="group"
        name="colours"
        :current-value="['blue']"
        :options="[
            {
              name: 'Blue',
              value: 'blue',
            },
            {
              name: 'Green',
              value: 'green',
            },
            {
              name: 'Orange',
              value: 'orange',
            }
        ]"
        v-model="fields.colours"
        :validation="['required', 'min:2', 'max:2']"
        :error="error"
        :disabled="isDisabled"
    ></checkbox-group-input>
    
</div>

Single select

<single-select
    :group="group"
    name="title"
    label="Title: *"
    v-model="fields.title"
    :options="[
        {
          name: 'Mr',
          value: 1,
        },
        {
          name: 'Mrs',
          value: 2,
        },
        {
          name: 'Ms',
          value: 3,
        },
        {
          name: 'Master',
          value: 4,
        },
        {
          name: 'Miss',
          value: 5,
        }
    ]"
    placeholder="Please select one"
    :focus="true"
    :validation="{
        'required': 'Please select your title',
        'in:1,2,3,4,5': 'Invalid selection'
    }"
    :error="error"
    :disabled="isDisabled"
></single-select>

Multi select (produces array)

With multi-select you can specify array of items representing current-value property to pre-select them when form first loads.

<multi-select
    :group="group"
    name="fruit"
    v-model="fields.fruit"
    :options="[
        {
            name: 'Apple',
            value: 'apple',
        },
        {
            name: 'Orange',
            value: 'orange',
        },
        {
            name: 'Grapefruit',
            value: 'grapefruit',
        },
        {
            name: 'Banana',
            value: 'banana',
        }
    ]"
    :validation="{ 'required': 'Please select your fruits' }"
    :error="error"
    :current-value="['banana', 'apple']"
    input-css-class="margin-bottom-0"
    :disabled="isDisabled"
></multi-select>

Text area

To allow us pass validation as well as content to the text-area component, we are using named slots with body representing content for the input and validation to replace default validation.

<text-area
    :group="group"
    name="excerpt"
    label="Excerpt: *"
    v-model="fields.excerpt"
    :validation="{
        'required': 'Please provide the excerpt',
        'min:3': 'Minimum 3 characters',
        'max:10': 'Maximum 10 characters',
    }"
    :error="error"
    :disabled="isDisabled"
>    
    <div slot="body">Content goes here</div>    
</text-area>

or with both, body and validation

<text-area
    :group="group"
    name="excerpt"
    label="Excerpt: *"
    v-model="fields.excerpt"
    :validation="['required', 'min:3', 'max:10']"
    :error="error"
    :disabled="isDisabled"
>   
    <template v-slot:body>Content goes here</template>    
    <template v-slot:validation>
        <form-validation
            id="colours"
            name="colours"
            :show="error.has('colours')"
            :validation="{
                'required': 'Please provide the excerpt',
                'min': 'Minimum 3 characters',
                'max': 'Maximum 10 characters',
            }"
            :error="error"
            css-class="block"
        ></form-validation>
    </template>    
</text-area>

CKEDITOR

The wysiwyg-editor component will render CKEditor window.

For this component to work, you have to make sure that the instance of CKEIDTOR is globally available i.e. import it in the header of your document <script src="//cdn.ckeditor.com/4.11.2/full/ckeditor.js"></script>.

The same way as text-area, you can pass the content and validation using named slots body and validation.

Make sure that the value you pass to body slot is encoded i.e. using htmlentities.

This component takes additional 2 properties components-css, which is the path pointing to the css file that should be styling wysiwyg content and general config to pass any additional CKEDITOR specific configuration options. For more information on config see CKEDITOR_config.

<wysiwyg-editor
    :group="group"
    name="body"
    label="Body: *"
    v-model="fields.body"
    :validation="{
        'required': 'Please provide the body'
    }"
    :error="error"
    contents-css="./assets/editor.css"
    :config="{ height: '30rem' }"
    :disabled="isDisabled"
>
    <template v-slot:body>&lt;h1&gt;Body&lt;/h1&gt;</template>
</wysiwyg-editor>

Switch input

// todo

Triggers

To interact with the form, you can either use buttons within the form-wrapper

<form-wrapper 
    group="update-form"
    action="/"
    v-slot:default="{
        group,
        fields,
        error,
        isDisabled,
        processing,
        reset,
        clear,
        disableEvent,
        enableEvent
    }"
    v-cloak
>    
    // ...

    <button
        type="submit"
        class="expanded button"
        v-show="!processing"
        :disabled="isDisabled"
    ><i class="fas fa-check fa-fw"></i> SUBMIT</button>
    <button
        type="button"
        disabled
        class="button"
        v-show="processing"
    ><i class="fas fa-spinner fa-spin fa-fw"></i> PROCESSING</button>
    
    <button
        type="button"
        class="secondary button"
        @click="disableEvent"
        v-if="!isDisabled"
    ><i class="fas fa-power-off fa-fw"></i> DISABLE</button>
    <button
        type="button"
        class="success button"
        @click="enableEvent"
        v-if="isDisabled"
    ><i class="fas fa-power-off fa-fw"></i> ENABLE</button>
    
    <button
        type="button"
        class="alert button"
        @click="reset"
        :disabled="isDisabled"
    ><i class="fas fa-eraser fa-fw"></i> RESET</button>
    
    <button
        type="button"
        class="warning button"
        @click="clear"
        :disabled="isDisabled"
    ><i class="fas fa-times fa-fw"></i> CLEAR</button>
    
</form-wrapper>

or outside of it. When using triggers outside of the form-wrapper you have to make sure that they have group property matching the form you're trying to bind them to.

You also need to specify what event should they fire on click. There are several events that a form-wrapper is listening to:

  • submit: submits the form
  • reset: resets the form input
  • clear: clears the form input
  • disable-started: sets isDisabled flag to true
  • disable-ended: sets isDisabled flag to false
<form-trigger 
    group="update-form" 
    fire="submit" 
    v-slot:default="{ isDisabled, trigger, processing }"
    v-cloak
>
    <span
        class="expanded button"
        :class="{ disabled: isDisabled }"
        @click="trigger"
    >
        <span v-if="!processing">
            <i class="fas fa-check fa-fw"></i> SUBMIT
        </span>
        <span v-else>
            <i class="fas fa-spinner fa-spin fa-fw"></i> PROCESSING
        </span>
    </span>
</form-trigger>

<form-trigger
    group="update-form"
    fire="disable-started"
    v-slot:default="{ isDisabled, trigger }"
    v-cloak
>
    <span
        class="secondary button"
        @click="trigger"
        v-if="!isDisabled"
    >
        <i class="fas fa-power-off fa-fw"></i> DISABLE
    </span>
</form-trigger>

<form-trigger
    group="update-form"
    fire="disable-ended"
    :always-enabled="true"
    v-slot:default="{ isDisabled, trigger }"
    v-cloak
>
    <span        
        class="success button"
        @click="trigger"
        v-if="isDisabled"
    >
        <i class="fas fa-power-off fa-fw"></i> ENABLE
    </span>
</form-trigger>

<form-trigger 
    group="update-form" 
    fire="reset" 
    v-slot:default="{ isDisabled, trigger, processing }"
    v-cloak
>
    <span        
        class="alert button"
        :class="{ disabled: isDisabled }"
        @click="trigger"
    >
        <span v-if="!processing">
            <i class="fas fa-eraser fa-fw"></i> RESET
        </span>
        <span v-else>
            <i class="fas fa-spinner fa-spin fa-fw"></i> PROCESSING
        </span>
    </span>
</form-trigger>

<form-trigger 
    group="update-form" 
    fire="clear" 
    v-slot:default="{ isDisabled, trigger, processing }"
    v-cloak
>
    <span
        class="warning button"
        :class="{ disabled: isDisabled }"
        @click="trigger"
    >
    <span v-if="!processing">
        <i class="fas fa-times fa-fw"></i> CLEAR
    </span>
    <span v-else>
        <i class="fas fa-spinner fa-spin fa-fw"></i> PROCESSING
    </span>
    </span>
</form-trigger>

Validation

// todo

Top Dialog

// todo

Master checkbox

// todo

Depending dropdowns

// todo

Additional attribute bindings

If you'd like to add some additional attribute bindings to your form input you can use :input-bindings attribute. Example would be adding step, min and max attributes to the number-input:

<number-input
    :group="group"
    label="Price: *"
    name="price"
    v-model="fields.price"
    :validation="{
        'required': 'Please provide the price',
    }"
    :error="error"
    :disabled="isDisabled"
    current-value="20.22"
    :input-bindings="{ step: '.01', min: 0, max: 1000.00 }"
></number-input>

Mutating values for the request

Sometimes you might want to have a value represented in one format, but send it in another. Example of such situation could be the price where input uses float format, but server expects integer i.e. pounds vs pence / dollars vs cents.

For this sort of scenarios you can use mutators attribute on the form-wrapper in the form of the object with keys representing the field to mutate and value - callback function to mutate the entered value.

<form-wrapper
  group="update-form"
  action="/"
  behaviour="redirect"
  v-slot:default="{
    group,
    fields,
    error,
    isDisabled,
    processing,
    reset,
    clear,
    disableEvent,
    enableEvent
  }"
  :mutators="{ price: value => parseInt(value.replace('.', '')) }"
  v-cloak
>
    //...
    
    <float-input
        :group="group"
        name="price"
        label="Price: *"
        v-model="fields.price"
        current-value="2257"
        :validation="{
            'required': 'Please provide the price',
            'integer': 'Invalid price format',
        }"
        :error="error"
        :disabled="isDisabled"
    ></float-input>

    //...
</form-wrapper>

The above form will send the request with value for price index an integer of 2257. Please note that current-value is also provided as integer type - this will automatically be converted to a float when the component is created and allows to pass the value directly from the database without necessity of manually converting it to the decimal point.

Float input has :decimals property that determines the number of decimal places, for example :decimals="3" would display 3 digits after the decimal point.

Bouncing

If you need the field to update with the values of another field when empty, you can use :bounce-of property:

<text-input
  :group="group"
  label="Contact card: *"
  name="contact_card"
  v-model="fields.contact_card"
  :validation="{
    required: 'Please provide your contact card',
  }"
  :error="error"
  :disabled="isDisabled"
  :bounce-of="(fields.first_name + ' ' + fields.last_name + ' <' + fields.email + '>')"
></text-input>
2.1.0

3 years ago

2.0.2

3 years ago

2.0.1

3 years ago

2.0.0

3 years ago

1.12.0

3 years ago

1.11.3

3 years ago

1.11.2

3 years ago

1.11.1

3 years ago

1.10.4

3 years ago

1.11.0

3 years ago

1.10.3

4 years ago

1.10.2

4 years ago

1.10.1

4 years ago

1.10.0

4 years ago

1.9.1

4 years ago

1.9.0

4 years ago

1.8.0

4 years ago

1.7.10

4 years ago

1.7.9

4 years ago

1.7.8

4 years ago

1.7.7

4 years ago

1.7.5

4 years ago

1.7.4

4 years ago

1.7.3

4 years ago

1.7.2

4 years ago

1.7.1

4 years ago

1.7.0

4 years ago

1.6.0

4 years ago

1.5.1

4 years ago

1.5.0

4 years ago

1.4.1

4 years ago

1.4.0

4 years ago

1.3.0

4 years ago

1.2.7

4 years ago

1.2.6

4 years ago

1.2.5

4 years ago

1.2.4

4 years ago

1.2.3

4 years ago

1.2.2

4 years ago

1.2.0

4 years ago

1.2.1

4 years ago

1.1.13

4 years ago

1.1.12

4 years ago

1.1.11

5 years ago

1.1.10

5 years ago

1.1.9

5 years ago

1.1.8

5 years ago

1.1.7

5 years ago

1.1.6

5 years ago

1.1.5

5 years ago

1.1.4

5 years ago

1.1.3

5 years ago

1.1.2

5 years ago

1.1.1

5 years ago

1.1.0

5 years ago

1.0.5

5 years ago

1.0.4

5 years ago

1.0.2

5 years ago

1.0.1

5 years ago

1.0.0

5 years ago

0.1.8

5 years ago

0.1.7

5 years ago

0.1.6

5 years ago

0.1.5

5 years ago

0.1.4

5 years ago

0.1.3

5 years ago

0.1.2

5 years ago

0.1.1

5 years ago

0.1.0

5 years ago