/vuetify-form-base

Schema-based Form Generator - Vue.js 2.0 Component based on Vuetify

Primary LanguageVueMIT LicenseMIT

Vuetify-Form-Base

Imagine you get the following data object in JSON format and have to edit it now.

Value: {
  name: 'Jumo',
  position: 'Coder',
  tasks: [
    { 
      done: true,
      title: 'make refactoring' 
    },
    { 
      done: false,
      title: 'write documentation'  
    },
    { 
      done: true,
      title: 'remove logs'  
    }        
  ]        
}

Normally you have to flatten the data structure and then map all to an appropriate form. Then you have to define a HTML-Form and animate it with your data.

With Vuetify-Form-Base create a Schema Object with the same structure as your Data.

Schema: {
  name: {type:'text', label:'Name', flex:{ xs:12, sm:6 } },
  position: {type:'text', label:'Position', flex:{ xs:12, sm:6 } },
  tasks: { type: 'array', flex:12, schema: { done:{ type:'checkbox', label:'done', flex:3}, title:{ type:'text'}, flex:9 } },
}  

and you will get a full reactive, editable Form:

Form

You have to create a lot of different Forms? You have to manipulate or edit Data presented in JS-Objects?

Then give Vuetify-Form-Base a Try. This Schema-based Form Generator is a Vue.js 2.0 Component and can simplify your Job by automatically creating full editable Forms. Edit this Forms and get reactive Results.

Vuetify-Form-Base uses the well known Component Framework Vuetify to style and layout your Form. Vuetify Controls have a clear, minimalistic design, and support responsive Design.


Demo

Look for an Example on Github

or

Download Project, change current directory to ../vuetify-form-base/example and then run

npm install

npm run serve


Intro

vuetify-form-base is a Vue Component and can easily integrated into any Vue Project.

The Schema-Object has the same structure as the Value-Object. Create a Schema by cloning the Value-Object and replace the Values of the Data-Object by Definitions for your your Schema. The corresponding Schema-Object defines type, layout and functional behaviour of your Form.

Form Example

The Component Framework Vuetify styles your Form. The Controls have a clear design, but don't worry you can change your style in a lot of ways. For more details see section Style with CSS

Based on an existing Value-Object vuetify-form-base generates a full editable Form. Layout and Functionality are defined in a Schema-Object, which has the same Property structure as the Value-Object. Your Data-Object keeps full reactive and any Input or Change in your Form triggers an Event too. If you have a deep nested Value-Object or an Array -Structure you can direct work on it. There is no need to flatten or modify your Data-Presentation.

Form Example

Changing any Field in the Form gives you a reactive Result in your Value-Object. Furthermore you can synchronize two or more Forms by using same Value-Object.

If you want a Partial-Form which displays only parts of your Data.Object, then link a property of your Data-Object to your vuetify-form-base Component.

And if necessary you can also build a Form in Form by using Slots.

Use the v-on directive of Vue.js to listen to Formbase triggered Events for 'Resize', 'Focus', 'Input', 'Click' and 'Swipe'. Listening to 'Update' will catch all Events.

Select Types from Vuetify UI Input & Controls like Textfield, Password, Email, Textarea, Checkbox, Radio, Switches, Sliders, Combobox, Autocomplete, Select, Combobox, Date- or Timepicker. There are some other types like 'array' and 'list'.

More Informations to Vuetify Textfields find here.


Installation

For proper working you need a Vue.js Project with Vuetify installed. For more Details see Vuetify Quickstart.

npm install vuetify-form-base --save

vuetify-form-base is a Vue.js single-file component with a .vue extension and you can use it like any Vue-Component.

In order for your application to work properly, you must wrap it in a v-app component. This component is required and can exist anywhere inside the body, but must be the parent of ALL Vuetify components. v-content needs to be a direct descendant of v-app.

<template>
  <v-app>
    <v-content>
      <v-container fluid>
        <v-form>
          <v-form-base :value="myValue" :schema="mySchema" />          
        </v-form>                  
      </v-container>      
    </v-content>
  </v-app>>      
</template>

import VFormBase from 'vuetify-form-base';  

export default {	
  components:{ VFormBase },
  data () {
    return {
      myValue: {
        name: 'Jumo',
        password: '123456',
        email: 'base@mail.com',
        checkbox: true,
        select: 'Jobs',
      },   
      mySchema: {
        name: { type: 'text', label: 'Name' },
        password: { type: 'password', label: 'Password' },
        email: { type: 'email', label: 'Email' },
        checkbox: { type: 'checkbox', label: 'Checkbox' },
        select: { type: 'select', label: 'Select', items: ['Tesla', 'Jobs', 'Taleb'] }
      }
    }
  }
}

and you will get a full editable Form based on your schema and filled with your Value-Object.

Basic Form

INFORMATION:

Properties in 'myValue' without corresponding Prop in 'mySchema', are ignored and keep untouched, but a initial warning will be logged to console


Example displaying nested Data-Object

In Reality sometimes you will have deep nested objects or arrays, which should be edited. vuetify-form-base works for you and flatten internally this nested object and build a plain Form.

  myValue: {
    name: 'Base',
    controls:{
      selection:{
        select: 'Tesla',
        selectM: ['Jobs'],
      },
      switch: [ true,false ],
      checkbox: [ false, true, { 
        checkboxArray: [ true, false ]}
      ]
    }       
  },

  mySchema: {
    name: { type: 'text', label: 'Name'},
    controls:{
      selection:{
        select: { type: 'select', label: 'Select', items: ['Tesla', 'Jobs', 'Taleb'] },        
        selectM: { type: 'select', label: 'M-Select', multiple:true, items: ['Tesla', 'Jobs', 'Taleb']}
      },
      switch: [ 
        { type: 'switch', label: '1' }, 
        { type: 'switch', label: '2' } 
      ],
      checkbox: [
        { type: 'checkbox', label: 'A' },
        { type: 'checkbox', label: 'B' }, 
        { checkboxArray: [
          { type: 'checkbox', label: 'C-A', color:'red' },
          { type: 'checkbox', label: 'C-B', color:'red' }
        ]}  
      ],
    }
  }

Form Example


Example editing Data-Arrays

For editing arrays use the type 'array' and define an nested 'schema' property.

mySchema: {
  tasks: {
    type: 'array',
    schema: { 
      done: { type: 'checkbox'  }, 
      title: { type: 'text' }
    }
  }  
}

Type Array - Schema object

myValue: {      
  tasks: [
    {
      done: true,
      title: 'make refactoring'
    },
    {
      done: true,
      title: 'write documentation'
    },
    {
      done: true,
      title: 'remove logs'
    }
  ]
},
mySchema: {
  tasks: {
    type: 'array',
    schema: { 
      done: { type: 'checkbox', label: 'Ok', flex: 3 }, 
      title: { type: 'text', flex: 8 },            
    }
  }
}

Form Example


Computed Schema

IF you want Schema Properties to be changed dynamic, then you must make your Schema Object a computed property. This Example turns the Radio Layout from Column to Row on Resizing to medium Size or greater.

data () {
  return {

    myValue: {
      radio: 'A',
    }

  }   
},
computed: {          

  mySchema(){ 
    return {
      radio: { type: 'radio', row: this.row, options:['A','B'] }
    }
  },

  row () {
    return this.$vuetify.breakpoint.mdAndUp 
  }
},

Vuetify Layout and Grid

Integrate Vuetify Grid by using the Schema-Properties 'flex', 'offset' and 'order':

mySchema: {
  name: { type: 'text', flex: 4, offset: 2, order: 1 },
}

flex: 4     // shorthand for flex: { xs:4 }
offset: 2   // shorthand for offset: { xs:2 }
order: 1    // shorthand for order: { xs:1 }

A more responsive Solution with 'flex', 'offset' or 'order' needs an Object as Value. For more Details see Vuetify Documentation:

Vuetify - Grid: flex: { xs:12, sm:8, md:6, lg:4 }

Vuetify - Offset: offset: { xs:0, sm:1, md:2, lg:2 }

Vuetify - Order: order: { xs:1, sm:1, md:2, lg:2 }

Link & Synchronize

Forms can be linked together using the same Value-Object. Changes in one Form are synchronized and reflected in the other Form.

<v-form-base :value="myValue" :schema="mySchema" />

<v-form-base id="form-sync" :value="myValue" :schema="mySchema" />

Vuetify Controls API-Props

Vuetify Controls have a API with Props These Props in Vuetify-Controls comes in kebab-case amd must for use in Schema-Object converted to CamelCase

<!-- vuetifyjs.com -->
Input & Controls
  Text-fields
    API-Props   
      append-icon
      background-color 

<!-- JS -->
mySchema: { 
  name: { type:'text', appendIcon:'menu', backgroundColor': 'red' },
  ...
}

Schema

<form-base :schema="schema" ... />

Schema is an JS-Object, which defines and controls the behavior of your Form. Each Key Prop) in your Schema-Object must reflect a Key from your Data-Object. A minimalistic Definition of a text input could look like this:

schema:{
  name: { type:'text'}  
}

The next shows a more complex Schema:

// Partials Functions for Rules
const minLen = l => v => (v && v.length >= l) || `min. ${l} Characters`
const maxLen = l => v => (v && v.length <= l) || `max. ${l} Characters`
const required = msg => v => !!v || msg
const validEmail: msg => v => /.+@.+\..+/.test(v) || msg

// Destruct Value and return a Value! 
const toUpper = ( {value} ) => value && value.toUpperCase() 

export default {

  components: { VFormBase },
  data () {
    return {
      
      myValue: {
        name: 'Base',
        password: '123456',
        email: 'base@mail.com'
      },
              
      mySchema: {
        name: { 
          type: 'text', 
          label: 'Name', 
          hint:'Converts to UpperCase'
          toCtrl: toUpper, 
          fromCtrl:toUpper,
          rules: [ required('Name is required<>) ] 
          flex: 12, 
        },
        password: { 
          type: 'password', 
          label: 'Password', 
          hint:'Between 6-12 Chars', 
          appendIcon: 'visibility', 
          counter: 12, 
          rules: [ minLen(6), maxLen(12) ], 
          clearable: true, 
          flex: 12 
        },
        email: { 
          type: 'email', 
          label: 'Email', 
          rules: [ validEmail('No valid Email'), required('Email is required<>) ], 
          flex: 12 
        }
      }
    }
  }
}

Available Properties in Schema

For further Props see Vuetify Controls API

schema:{
  
  type: string            // text, password, email, radio, switch, slider,
                          // combobox, autocomplete, select, combobox, date, time,  ...    

  sort: N                 // use simple order to display items 
  order: N or Object      // use Vuetify-Grid to order items responsive 

  flex: N or Object       // See Vuetify Grid
  offset: N or Object     // See Vuetify Grid

  label string,           // label of item    
  placeholder: string,    // placeholder 
  hint: string,           // additional Info  

  color: string
  backgroundColor:string
  css: string,            // inject classnames - schema:{ name:{ css:'small'}, ...  }
    
  mask: string,           // regex to control input  

  multiple: bool,         // used by type: select, combobox, autocomplete    
  required: bool,         // need an input value
  hidden: bool,           // hide item - set from another item
  disabled: bool,         
  readonly: bool,          
        
  appendIcon: icon        // click triggers event with icon-location
  prependIcon: icon       // click triggers event with icon-location

  items: array            // ['A','B'] used by type: select, combobox, autocomplete   
  options: array,         // ['A','B'] used by type:radio
  rules: array of Fn      // [ value => true || false, ... ]
  
  // must return a (modified) value!!
  toCtrl: function,       // ( {value, obj, data, schema} ) => value	
  fromCtrl: function,     // ( {value, obj, data, schema} ) => value
}

Events

We can use the v-on directive to listen to vuetify-form-base events 'focus', 'input', 'click', 'resize', 'swipe', 'update' and run some Code when they’re triggered.

This Example use the Default ID and listen all events with 'update':

<!-- HTML -->
<v-form-base :value= "myValue" :schema= "mySchema" @update= "update" />

This has a Custom ID and listen all events in separate methods. Your v-on Directive must append the Custom ID:

<!-- HTML -->
<v-form-base id = "form-base-simple" :value= "myValue" :schema= "mySchema" @update:form-base-simple= "update" />
<v-form-base 
  id = "form-base-complete"
  :value= "myValue" 
  :schema= "mySchema"  
  @resize:form-base-complete= "resizeCode"
  @focus:form-base-complete= "focusCode"
  @click:form-base-complete= "clickCode"
  @swipe:form-base-complete= "swipeCode"
  @input:form-base-complete= "inputCode"
/>

The Event-Signature:

update( { on, id, key, value, obj, event, params, size, data, schema } ){
  // destructure the signature object 
  // ... on, id, key, value, obj, event, params, size, data, schema 
}

on - Trigger Name   // focus | input | click | resize | swipe or update to listen all 
id - Formbase-ID
key - key of triggering Element
value - value of triggering Element
obj - triggering Element { key, value, schema } 
params - params object if available { x, y, pos, icon }    
event - the native trigger-event if available 
data - Data-Object
schema - Schema-Object

Example: Use 'Update' Event to control Visibility of Password Element

<!-- HTML -->
<v-form-base :value="myValue" :schema="mySchema" @update="update">

<!-- JS -->
// Schema
mySchema: {
  password:{ type:'password', appendIcon:'visibility', .... }
}

...

update ({ on, key, obj, params }) {
  // test event is 'click' and comes from appendIcon on key 'password'
  if (on == 'click' && key == 'password' && (params && params.pos) == 'append') {         
    // toggle icon
    obj.schema.appendIcon = obj.schema.type === 'password' 
      ? 'lock' 
      : 'visibility'

    // toggle visibility 
    obj.schema.type = obj.schema.type === 'password' 
      ? 'text' 
      : 'password'
  }
}

Slots

Use Slots to pass Header and Footer into a Control. If necessary replace Controls by Slots. Any slot could be a v-form-base component itself.

<v-form-base :value="myValue" :schema="mySchema" @update="update">

  <h4 slot="slot-top-key-name">Top Slot on Key Name</h4>
  <h4 slot="slot-top-type-radio">Top Slot on Types Radio</h4>
  
  <h4 slot="slot-item-key-password">Slot replaces Key Password</h4>
  
  <h4 slot="slot-bottom-key-name">Bottom Slot Key Name</h4>
  <h4 slot="slot-bottom-type-radio">Bottom Slot on Types Radio</h4>

</v-form-base>

Slots in Blue


If you need Form Validation you have to wrap v-form-base with v-form and take the reference of v-form for working on.

<!-- HTML -->    
<v-form ref="form" v-model= "formValid" lazy-validation>
  <v-form-base :value= "myValue" :schema= "mySchema" @update= "update"/>
</v-form>

<!-- JS -->
validate () {
  this.$refs.form.validate()
},

resetValidation () {
  this.$refs.form.resetValidation()
},

Style with CSS

Customize your vuetify-form-base component using CSS-Classnames

IMPORTANT:
Don't use <style scoped> in parents component, because scoped definitions are inside the child component not accessable

Formbase - ID

#form-base is the default ID of your component. If you need different CSS for two or more forms in the same parent component, then change default value by setting a different ID for each component and use this new ID. Using a 'custom-id' you have to modify the event-binding to @update:custom-id = "update"


<!-- Default ID CSS-Style -->   	
#form-base {...} 

<!-- HTML-Template -->   
<v-form-base @update= "update" />  

<!-- Custom-ID CSS-Style -->   	
#custom-id {...} 

<!-- HTML-Template -->   
<v-form-base id="custom-id" @update:custom-id= "update" />  

General - Classname

#form-base  {...}              

Type - Classnames

Style all items of a specific type, then use type specific classnames. They start with type- appended by any type. You can use following types in your Schema-Object:

'text', 'email', 'password', 'textarea', 'select', 'autocomplete', 'combobox', 'radio', 'checkbox', 'slider', 'switch', 'date', 'time'

#form-base .type-text { color: #44A }}           	  
#form-base .type-email { font-weight:500; }  

Key - Classnames

Set Classname of deep key in your Data-Object, by converting .dot notation 'person.adress.city' into kebab case 'person-adress-city' prepending 'key-'

<!-- 
    myValue{ person:{ adress:{ city:'',... } ... } ... } 
  CSS Classname to access to key 'city'
-->
#form-base .key-person-adress-city { font-weight:500; }    


<!-- 
  Access to myValue: { name:'' }
  CSS Classname to access key 'name' 	       
-->
#form-base .key-name { font-weight:500; }            

<!-- 
  myValue: { controls: { slide: [25, 64]  } 
  Access First Entry in Array of Key Slide 
-->
#form-base .key-controls-slide-0 { font-weight:500; }  

Validate with Pseudoselectors

#form-base .item input:valid { background-color: #afa; }
#form-base .type-email input:invalid { background-color: #faa; }
#form-base .key-name input:focus { background-color: #ffd; }   

CSS - Example

<!-- JS -->
myValue: {
    name: 'Base',
    password: '123456',
    email: 'base@mail.com',
    controls: {
      checkbox: true,
      switch: true,
      slider: 33,
      radioA: 'A',
      radioB: 'B'
    }
  }

 <!-- CSS  -->
<style>
  #form-base { 
    border: 1px solid #cb2; 
    background-color: #ffe; 
    padding:2rem 
  }
  
  /* CSS Item --- set all items  */
  #form-base .item { 
    border-left: 1px dashed #29D; 
    border-top: 1px dashed #29D; 
    padding:1rem 
  }

  /* CSS Type --- set all items with type */
  #form-base .type-switch { border-bottom: 3px solid #E23}
  #form-base .type-checkbox { background-color: #fdd }

  /* CSS Keys --- select key in object 'myValue.controls.slider' */
  #form-base .key-controls-slider { background-color: #fec }
</style>

Slots in Blue


Features

  • Vue-Component
  • integrates UI framework Vuetify with responsive Layout and Support of Grid
  • Use a lot of Vuetify Control & Input types inclusive available API-Props
  • Get full configurable Forms based on Schema Definition
  • Edit plain or deep nested objects including Arrays, without the Need to flatten it
  • Get a Full reactive Result
  • Listen on 'Resize', 'Focus', 'Input', 'Click', 'Swipe' and 'Update' Events
  • Use Slots to pass Header and Footer into a Control. Or replace a Control by Slot
  • Configurable CSS Style

Dependencies

Vue >= 2.4

Vuetify >= 1.4

Lodash > 4.0


Similar Projects

vue-form-generator

vue-formular


License

vuetify-form-base is available under the MIT license.