vue-form-base
Version:
Form-Generator: a Vue 2.0 component, reactive Result using Vuex, responsive Display
498 lines (309 loc) • 13 kB
Markdown
Vue-Form-Base
===
Vue-Form-Base is a Vue 2.0 Component and Form-Generator for editing plain or deep nested Javascript objects getting a reactive Result.
You have to create a lot of different forms? **Vue-Form-Base** can simplify your job by creating forms from JS-Objects.
Select from different HTML5 Input-types like Text, Password, Checkboxes, Radios, Select, Multiselect, Color, Files or a lot of other fields. Apart from 'select' or multiselect' the Browser specific implementation of **Input-Types** is used. [Some Informations to HTML5 input-types here](https://www.wufoo.com/html5/)
We use the [Materialize CSS](http://materializecss.com/) framework for styling. Input Fields have a clear, minimalistic design, but don't worry you can change your style with CSS in a lot of ways. For more details see section **Style with CSS**
Add global **Validation** to the form or validate only a single field. Use inline validation or write a new function for individuell validation.
Make complex data editable by **mapping** your incoming and outgoing data: i.e. change dateformats, trim strings or join/split arrays for editing in a textfield.
And finally get a full reactive Result by using **Vuex.**
Installation
===
npm install vue-form-base --save
Using [single-file components](https://vuejs.org/v2/guide/single-file-components.html) with a .vue extension,
import Formbase.vue File from your path
import FormBase from 'vue-form-base';
then register in
export default {
...
components:{
FormBase
}
}
and use it in template
<template>
<form-base :data="data" :schema="schema" data-state-name="datastate" />
</template>
**Minimalistic Example**
If your **data-state-name** property has the value *datastate*,
then you must additionally define `datastate` in Vuex State.
State:{
datastate:null,
...
}
Now use existing Data Object
data:{
name: 'smith',
email:'smith.com'
}
define the following minimalistic Schema
schema:{
name: {type:'text'},
email: {type:'email'}
}
and get this full editable Form

**Edit using different Input-Types**
A more realistic Example.
Your Data to edit:
data:{
name: 'smith',
email:'smith',
password: '12345ABCDEF',
remember: 'undefined',
adress:{
city:'NY',
}
},
Create a Schema Object:
schema:{
user: {type:'text', label:'User:', placeholder:'User...',
mapSet: (val, obj, data, schema) => {
// type 'hide' to hide dependent item password
schema.password.hidden = val === 'hide';
return val;
}
},
email: {type:'email',label:'Email:', validate:true },
password: {type:'password', label:'Password(Numbers only):', pattern:'[0-9]*', validate: msg => console.log(msg) },
remember: {type:'checkbox', label:'Remember Me:', true:'Yes', false:'No' },
adress:{
city:{ type:'text', mapSet: v => v && v.toUpperCase() }
}
}

>IMPORTANT:
>
>Properties from Data-Object, which doesn't exist in Schema-Object, are ignored.
Reactive Result (Vuex)
===
Model `Data` und describing `Schema` flow as prop into the `Vue-Form-Base`. On the concept of *one-way data flow* you get reactive access to your modified data via Vuex Store `$store.state.datastate` . Installed Vuex is mandatory, details about Vuex you can find here [Vuex-Introduction](https://vuex.vuejs.org/en/intro.html)
<form-base :data="data" :schema="schema" data-state-name="datastate" />
If you need to dynamically modify the internal Schema (for example if you want to change dynamically `schema.hidden` to show/hide one item depending from the input of another item) you can have reactive access to the modified Schema via Vuex Store `$store.state.schemastate`.
Inside a single component .vue file you can use your component like this
<template>
<form-base
:data="data"
:schema="schema"
data-state-name="datastate"
schema-state-name="schemastate"
>
</form-base>
</template>
Get **Access** to the reactive Result using Vuex State anywhere in your Project.
this.$store.state.datastate
> IMPORTANT:
>
> 'Data' and 'Schema' passed as Prop becomes not mutated.
Two Forms can be reactiv **Linked** by using the same state property
<form-base :data="data" :schema="schema" data-state-name="dataCommon" />
<form-base :data="data" :schema="schema" data-state-name="dataCommon" />
and if you need different CSS
<form-base id="form1" :data="data" :schema="schema" data-state-name="dataCommon" />
<form-base id="form2" :data="data" :schema="schema" data-state-name="dataCommon" />
**Reset** modified Data and modified Schema use following code inside the parent single component .vue file
If you need to change the Schema-Object dynamically, like in this case hiding another item
schema:{
...
user: {
type:'text',
mapSet: (val, obj, data, schema) => { schema.password.hidden = val === 'hide'; return val }
},
password: {
...
}
}
then you need `datastate` and `schemastate` to restore like in this case
...
methods:{
reset(){
this.$store.state.datastate= cloneDeep(this.data);
this.$store.state.schemastate= cloneDeep(this.schema)
},
...
},
...
In common use cases without modified Schema **Reset** can be shortened
<template>
<form-base :data="data" :schema="schema" data-state-name="datastate" />
</template>
<script>
import { cloneDeep } from 'lodash'
export default {
...
methods:{
reset(){
this.$store.state.datastate= cloneDeep(this.data);
},
...
},
...
</script>
Schema
===
<form-base :schema="schema" ... />
Schema is an object, which defines and controls the behavior of your form. Each Key in your schema-object must reflect a key in the data-object.
schema:{
user: { type:'text'}, // minimalistic definition of input-type
}
// more
validate = val => console.log(val);
mapSet = val => val + '!'
schema:{
user: {
type:'text',
label:'User:',
pattern:'([A-Z]*)',
css:'blue',
validate, // is the same as - validate:validate,
mapSet, // is the same as - mapSet:mapSet,
order:1
},
...
}
In common use cases the the value will be another object with several properties, to get different control over the behaviour of your input-field.
**Properties in Schema**
schema:{
order: number, // controls order of displaying
type: string, // 'text', 'password', 'email', ...
label: string, // title of item
placeholder: string, // placeholder set null to hide
true: string, // text if checkbox is checked
false: string, // text if checkbox is unchecked
accept: string, // type:'file' - limit files audio/*, image/*, .pdf
title: string, // define your own validation message
error: string, // preset/set inline error msg
css: string, // inject one or more classnames at item level
// Use 12 column grid system from materializecss.com/grid.html for displaying items
// for example a 12 column grid with 3 items in one row would look like:
// schema:{ item1:{ css:'col s4'}, item2:{ css:'col s4'}, item3:{ css:'next-row col s4'} }
pattern: string, // regex to control input
min: number, // limit number or range
max: number, // limit number or range
step: number,
maxlength: number, // max length of type text, password, email
multiple: bool, // type:'file' select one or more files
required: bool, // need an input value
disabled: bool, // disable input field
readonly: bool,
hidden: bool, // hide item - set from another item
options: array, // used with type: radio, list, text, select, mselect
mapGet: function, function( val, obj, state, schema ) {... return val}
mapSet: function, function( val, obj, state, schema ) {... return val} }
validate: true // use inline error message
validate: function, function( msg, obj, state, schema, validity ) {...}
noValidate: function, function( value, obj, state, schema ) {...}
}
**Real-Life Example**
...
schema:{
user: { type:'text', label:'Fullname:', placeholder:'Name...', css:'col s6'},
email: {type:'email', validate:true, mapSet: v => v && v.toUpperCase(), css:'next-row col s6' },
singleRole:{ type:'select', options:['Admin','Guest','User'] },
}
...
Style with CSS
---
Customize your **vue-form-base** component using the following CSS-Classnames
> IMPORTANT:
> Don't use `<style scoped>` in parents component, because scoped definitions
> are inside the child component not accessable
**Form-ID**
`#form-base` is the default form-id. 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
/* default */
<form-base ... />
#form-base .collection {...}
/* individualize it */
<form-base id="my-custom-id" ... />
#my-custom-id .collection {...}
**General - Classnames**
#form-base .collection {...} // style container for all items
#form-base .item {...} // each key is represented, by an item
#form-base .error {...} // style inline-error messages
**Validate with Pseudoselectors**
#form-base .item input:invalid { background-color: #fdd; }
#form-base .item input:valid { background-color: #dfd; }
#form-base .item input:focus { background-color: #ffd; }
**Type - Classnames**
Style all items of a specific type, then use type-classnames. They start with a type ie. `password` appending `-type`. Then you have a classname `password-type`
#form-base .text-type {...} or #form-base .item.text-type {...}
You can use most of HTML5 input-types like `<input type="password" />`. [Some Informations to HTML5 input-types](https://www.wufoo.com/html5/)
/*
Available Types:
text, password, email, select, multiselect, list, file, radio,
checkbox, number, range, url, date, time, week, month
use class by appending -type -> .text-type
*/
#form-base .text-type { font-weight:500 }
#form-base .item.select-type {...}
#form-base .item.multiselect-type {...}
#form-base .item.checkbox-type {...}
**Make validate CSS for multiselect type**
If you want to style select or multiselect types you have append `select` after the classname
#form-base .item.multiselect-item select { height:6rem }
**Key - Classnames**
Here you get direct access to each key in your Data-Object. If you want access deep nested keys youst must use a hyphen
data{ user:{ adress:{ city:'',... } ... } ... }
access deep nested key 'city' with CSS
#form-base .item.user-adress-city-key {...}
Example
===
a)
Use Example with vue-webapp template, checkout example directory in this repo and follow the readme.
b)
Here is a working Vue-File, you can integrate this in your vue-project. Lodash and Vuex must be installed. In initial Vuex state definition `state.datastate ` must exist
<style>
#form-base .error { color:red}
#form-base .item input:invalid{ background-color: #fdd; }
</style>
<template>
{{$store.state.datastate}}
<form-base :data="data" :schema="schema" data-state-name="datastate" />
</template>
import FormBase from './FormBase.vue';
export default {
data () {
return {
data:{
user: 'smith',
pw: 'secret'
},
schema:{
user: {
type:'text',
label:'User:',
placeholder:'User...'
},
pw: {
type:'password',
pattern:'^.{6,}',
lable:'Password:',
title: 'Password must have minimal 6 Chars',
required:true,
validate:true
}
}
}
},
components:{
FormBase
}
}
Features
===
* vue-component
* edit plain or deep nested objects
* support most of input types
* full reactive result
* configurable via Schema Defintion
* configurable Style based on Materialize CSS
Dependencies
===
* Vue 2.0
* Vuex
* Lodash
* Materialize CSSs
Similar Projects
===
[vue-form-generator](https://github.com/icebob/vue-form-generator)
[vue-formular](https://github.com/matfish2/vue-formular)