Examples

FieldFor

FieldFor component is the last component in the hierarchy over which the developer has control. This component is responsible for rendering all the form elements based on passed props.

You can use FieldFor in two ways.

  1. Standalone
  2. As a slot to FormFor

For Standalone way, the component at-least expects type, label, value and display-mode properties.

Two binding can be done on value variable by putting a .sync modifier. Alternatively an event @changed can also be listened to, in case you want to listen to any changes being done in form-element's value.

Note: If there is already a display-mode reactive data variable available in the parent scope, then passing it as a prop is not necessary.

<FieldFor 
    Type="Text" 
    label="Name" 
    :value.sync="name" 
    display-mode="EDIT"
    @changed="fetchAllCustomers">
</FieldFor>
export default {
    name: 'Details',
    data() => ({ name: '' });
}

For using FieldFor inside FormFor , let us first see what FormFor is.

FormFor

FormFor acts as a slot-friendly parent wrapper around all your FieldFor or custom components. It reads the schema passed to it and tries to render the specified element by looking into model properties as well as local props.

Certain Props will always have the higher priority over schema properties. Mandatory properties required to use with FormFor are model , data and display-mode.

FormFor has a special use case when it comes to validation. It reads through all its children slots and gathers the errors if any. You can use slot-scope which exposes invalid flag and errors array via v-slot.

<FormFor 
    :data="project"
    :display-mode="displayMode" 
    v-slot="form">
    <FieldFor field="Name" label="Customer Name"></FieldFor> 
    <FieldFor field="Description"></FieldFor>
    <FieldFor field="Budget" :min="0" :max="1000000" placeholder="Please enter your age"></FieldFor> 
    
    <Button text="Save" type="primary" v-show="displayMode === 'EDIT'" :action="save" :disabled="form.invalid" :async="true">
        <i class="material-icons">save</i>
    </Button>
</FormFor>

You can also destructure the v-slot="form" here with {errors, invalid}.

import Project from '@/models/Project';
export default {
    data() {
        return {
            project: new Project(),
            displayMode: 'EDIT'
        }   
    }
}

Note: Default display style is vertical, label followed by form-control. If you need a side by side design, then you can add form-horizontal class to FormFor component.

Format of the model/schema

A schema property must have at-least a type which depicts the type of element to be rendered dynamically through FieldFor.

const _schema = {
    Name: {
        type: String,
        required: true
    },
    Description: {
        type: String,
        textarea: 4   
    },
    StartDate: {
        type: Date,
        required: true
    },
    BudgetCost: {
        type: Number,
        filter: 'currency'
    },
    Status: {
        type: String,
        enum: ['Planned', 'Live', 'Dormant']
    },
    IsActive: {
        type: Boolean
    },
    ImageUrl: {
        type: String
    }
}
export default class Project extends Model {
    constructor(data) {
        super();
        Object.assign(this, data);

        // Any virtual properties of the model should be configured here.
        // Virtual properties must start with _

        this._ImageUrl = this.getProjectImageUrl();
    }
    schema() {   // This is a mandatory method.
        return _schema;
    },
    getProjectImageUrl() {
        if (this.ImageUrl) {
            return window.bucketendpoint + this.ImageUrl;
        } else {
            return require('@/assets/images/placeholder.png');
        }
    }
}

FormFor Chaining

  • In certain situations like nested schemas or nested arrays, it's best to utilise FormFor with chaining due to Vue's slot capabilities.
  • You can chain multiple FormFor within a FormFor as each FormFor exposes its own Scoped-Slot with errors and invalid flag. You can also use refs instead of scoped-slots.

An example is shown below

<FormFor :data="project" display-mode="EDIT" v-slot="form1">
    <button :disabled="form1.invalid">Submit</button>
    <FieldFor field="Name"></FieldFor>
    <FieldFor field="Description"></FieldFor>
    <FormFor :data="project.Address" display-mode="EDIT" v-slot="form2">
        <FieldFor field="Line1"></FieldFor>
        <FieldFor field="Line2"></FieldFor>
        <FormFor :data="project.Address.Location" display-mode="EDIT" v-slot="form3">
            <FieldFor field="Longitude"></FieldFor>
            <FieldFor field="Latitude"></FieldFor>
        </FormFor>
    </FormFor>
</FormFor>

Note: When you need a combined or merged errors, you can keep a ref on each FormFor and keep either a computed property or bind the expression directly. Here note that refs will only be available once the component is mounted so it is required to keep a conditional check to overcome $refs.

<button :disabled="checkInvalid">Submit</button>
<FormFor :data="project" display-mode="EDIT" ref="form1">
    <FieldFor field="Name"></FieldFor>
    <FieldFor field="Description"></FieldFor>
    <FormFor :data="project.Address" display-mode="EDIT" ref="form2">
        <FieldFor field="Line1"></FieldFor>
        <FieldFor field="Line2"></FieldFor>
        <FormFor :data="project.Address.Location" display-mode="EDIT" ref="form3">
            <FieldFor field="Longitude"></FieldFor>
            <FieldFor field="Latitude"></FieldFor>
        </FormFor>
    </FormFor>
</FormFor>
computed: {
    checkInvalid() {
        return (this.$refs.form1 && this.$refs.form1.invalid) || 
               (this.$refs.form2 && this.$refs.form2.invalid) || 
               (this.$refs.form3 && this.$refs.form3.invalid) 
    }
}

Presence of certain properties within a model will force a particular element to be rendered.

enum with Array: This will render Dropdown list.

textarea: Based on the integer value passed, it will set the rows of text area.

phone: If set to true, it will render the vue-tel-input component.

Some extra properties which can be set in the models include filter, maxlength, min, max, required, calendarConfig. More options can be passed via individual FieldFor props. You can find all the props in the Props Configuration.

Re-render/Force Reactivity

  • In certain cases when you would need to re-render a component to trigger the reactivity, you might want to re-render a FieldFor or a FormFor component. How you can do it ?
  • Vue triggers the component to re-render if at-least one of its prop changes and best way to do it is binding a counter to key prop and increasing it when needed.

Let's take an example, we need to re-render a Total Cost when Quantity and CostPerQuantity changes.

<FieldFor field="Quantity" @changed="quantityChanged"></FieldFor>
<FieldFor field="CostPerQuantity" @changed="costPerQuantityChanged"></FieldFor>
<FieldFor field="TotalCost" :key="counter"></FieldFor>
import Cost from '@/models/Cost'
export default {
    data() {
        return {
            counter: 0,
            cost: new Cost({
                Quantity: 0,
                CostPerQuantity: 0,
                TotalCost: 0
            })    
        };
    },
    methods: {
        quantityChanged(val) {
             this.cost.TotalCost = val * this.cost.CostPerQuantity;  
             this.counter++; 
        },
        costPerQuantityChanged(val) {
             this.cost.TotalCost = val * this.cost.Quantity;   
             this.counter++;
        }   
    }
}

In case of FormFor, you can follow the same approach too. It always helps when you have to rollback upon clicking on cancel, update the data and then update the counter linked as key to FormFor and FormFor will take care of roll-back.

<FormFor :data="cost" :display-mode="displayMode" :key="counter">
...
</FormFor>
cancel() {
    this.displayMode = 'VIEW';
    this.cost = new Cost(this.prevCost);
    this.counter++;
}

Note : FieldFor is reactive bottom-up (from Child to Parent) unlimited times but from top-down (Parent to Child) it's always first time reactivity (Exceptional cases like Dropdown).

All the examples below are based on FormFor usage, but you can use type and value properties in case you are planning to use standalone FieldFor.

TextBox

Standalone: type="Text"

Through model: { type: String }

<FieldFor 
    field="SubTitle"
    id="subTitle"
    tab-index="0"
    label="Sub title" 
    placeholder="Please add a sub title"
    filter="truncateChars"
    :filter-args="[30]"
    :required="true"
    :custom-class="text-left"
    :show-suggestion="true"
    :suggestions="suggestedSubTitles"
    @changed="valueChanged">
</FieldFor>

Number

Standalone: type="Number"

Through model: { type: Number }

<FieldFor 
    field="BudgetCost"
    id="budgetCost"
    tab-index="0"
    label="Budget Cost" 
    placeholder="Please add a budget"
    prepend="£"
    filter="currency"
    :filter-args="['GBP', 2]"
    :required="true"
    :custom-class="text-right"
    :min="0"
    :max="100000"
    @changed="budgetChanged">
</FieldFor>

Toggle

Standalone: type="Boolean"

Through model: { type: Boolean }

<FieldFor 
    :field="IsActive" 
    label="Activated ?"
    @changed="valueChanged">
</FieldFor>

Standalone : type="Select" & select-from prop

Through model: {type: String} in model & select-from prop

Note: Keeping enum array in the model also would populate dropdown list.

<FieldFor 
    :field="Client" 
    label="Choose Client" 
    :select-from="clients" 
    placeholder="Start typing to search"
    display-field="Name"
    value-field="ClientId"
    :searchable="true" 
    :allow-clear="true"
    :show-avatar="true"
    avatar-prop="ImageUrl"
    @changed="dropDownValueChanged">
</FieldFor>

If select-from is array of object, by default the Dropdown tries to display Name and Description props, however please refer below to customization guide.

Customization:

display-field : This property of the selected item will be displayed to the user. By default it is set to Name.

value-field : This property of the selected item sets the value for two way binding in the dropdown. By default it is set to _id.

full-object : If set to true, then whole object is set as value for two way binding in the dropdown.

searchable : If set to true, then the dropdown becomes searchable.

option-template : In case you need a custom template to be put, please make sure to set below option in vue.config.js.

runtimeCompiler: true

select-from options will be available as {{ props.option }}

Here is an example,

taxOptionsTempate: `
    <div class="description">
        <div class="title brand-primary" v-text="props.option.Name"></div>
        <small class="text-dark mb0" v-if="props.option.Description" style="display: block">
            {{ props.option.Description }}
        </small>
        <small v-for="(comp, key) in props.option.TaxComponents" :key="key">
            {{ comp.Name }} : <span class="brand-primary">{{ comp.Rate }} %</span>
        </small>
    </div>
`,

Multi Select

Standalone : type="Multiselect" & select-from

Through model : {type: Array} in model or multiple="true" and select-from prop

Rest is similar as above.

DatePicker

Standalone: type="Date"

Through model: { type: Date }

<FieldFor 
    label="Date Of Birth" 
    field="DateOfBirth" 
    :calendar-config="calendarConfig"
    @change="dateChanged">
</FieldFor>

To check for possible calendar-configurations, please visit vue-flatpickr Component

Phone

Standalone: `type="Phone"

Through model: {type: String, phone: true}

<FieldFor 
    field="Mobile" 
    :phone="true"
    :show-flags="false">
</FieldFor>

Props Configuration

Name Description Scope Type
field defines the field of the schema for which element will be rendered global with FormFor String
type defines the type of the element which will be rendered global with standalone String
value binds the value to the field. use .sync for two way communication global with standalone any
label name of the label to be shown above the element global String
required boolean property defines whether the element is a required field or not global Boolean
placeholder shows the value as placeholder global String
customClass appends the css class to the element in EDIT mode global String
customStyle appends the custom style to the element in EDIT mode global Object
disabled setting this to true will disable the element from user input global Boolean
displayMode display mode to be either VIEW, CREATE or EDIT global String
hideLabel hide the label from the form-group global Boolean
showValidationIndicators shows the custom validation around the border of the elements based on error, success, filled, active global Boolean
filter applies the passed filter when display-mode is VIEW global String
filterArgs applies the passed arguments to called filter when filter prop is passed global Array-Like
regex applies the regex to the value for validation Text field new RegExp
maxlength applies the maximum allowed characters for validation Text/Textarea field Number
showSuggestion sets the input element in suggestion mode Text field Boolean
suggestions list of suggestions from which most matchable suggestion will be populated Text field Array
min minimum value Number field Number
max maximum value Number field Number
prepend input group button will be prepended to show currency, percentage etc Number field String
rows value of this param defines the size of text area textarea Number
selectFrom value of this property renders the options of dropdown list - must be an array Dropdown Array
displayField when dropdown list has objects as options, property passed as displayField shows the value of selected element Dropdown String
valueField when dropdown list has objects as options, property passed as valueField acts as primary key and sets the value Dropdown String
fullObject when dropdown list has objects as options, setting this property as true will set whole object as value instead of primary key Dropdown Boolean
searchable setting this property as true will add a search option with a textbox for dropdown list Dropdown Boolean
allowClear setting this property as true will display an icon-button to clear dropdown's selected value Dropdown Boolean
showAvatar setting this property as true will display an avatar based on avatar-props. See below Dropdown Boolean
avatarProp this property sets the image's src attribute which is responsible for displaying avatar Dropdown String
optionTemplate template for displaying dropdown items. Please check Dropdown section above for more details Dropdown String
multiple setting this property as true will turn dropdown list into a multi select Multiselect Boolean
phone Setting this as true turns your element into telephone supported field FormFor with property type as string Boolean
showFlags Setting this as false hides national flags from telephone supported element. (Saves 122KB worth of css) Phone Field Boolean
calendarConfig Date pickr configuration. Check options Date Field Object

Note: FieldFor treats every other prop (which is not available above table) as $attrs. This is useful for passing ARIA accessibility attributes or native HTML properties like name, id etc which will be directly binded with HTML Element.

Slots

  • label - to override the label within the field-for, you can pass a slot with label
<FieldFor type="Text" :value.sync="name" display-mode="VIEW">
    <template v-slot:label>
        <div class="icon-wrapper">
            <i class="material-icons">edit</i> Name
        </div>
    </template>
</FieldFor>
  • view - to override the view element within the field-for when display-mode is set to VIEW, you can pass a slot with view
<FieldFor type="Text" :value.sync="address.Line1" display-mode="VIEW">
    <template v-slot:view>
        <p class="google-address">
            {{ address.Line1 }} ,
        </p>
    </template>
</FieldFor>

Custom Messages

You can set custom messages for errors when setting up the plugin.

Vue.use(VueFormPlugin, {
    formFor: true,
    messages: {
        required: 'Filling this field is completely required'
    }
});
Vue.use(VueFormPlugin, {
    formFor: true,
    messages: {
        required: (props, property) => `Filling ${props.label ?? property.name} field is completely required`
    }
});

Possible set of error messages include email, length, required, regex, min, max, phone, default keys.

In Javascript, you can also access messages via this.$messages .