# Getting Started

EOM+ form is a library that allows you to generate schema-driven forms. It has been inspired by project like vue-form-generator, dynamic-schema-vuelidate or formvuelatte

We create this to help us to solve real life forms problems into real life projects. I mean that EOM+ form is not the fastest form processor neither the lightest but it does the job. We use it in production with Laravel REST api as backend.

# Screenshots

Basic Form

# Features

  • reactive forms based on schemas
  • group and repeat form elements
  • over 20 built-in validators
  • use bootstrap-vue components and styles

# What we would like to implement

  • Dependency model like in XForms
  • Expressions into JSON
  • Framework7 version
  • Formbuilder

# Dependencies

eom-form uses mainly

moreover it uses other components like

we also include open source icon fonts such font awesome 5, ionicons,open iconic, stroke icons 7, linearicons

# Installation

# NPM

You can install it via NPM or yarn.

$ npm install eom-form

# Usage

# EomForm props

The EomForm component requires you to pass it schema, model and ref properties.

The schema can be both an object or an array, internally it will be transformed to an array. You can embed directly the JSON data, import it from local file or fetch it from your rest API.

The model are the data themselves. The payload that you will submit to your API. For complex forms with repeat blocks you must declare an empty structure to get it working. As with schema prop, you can embed, import or fetch the content.

Finally, you must set a ref property as it's necesarry to be able to call EomForm's validate method on submit form.

# Main file (App.vue)

  <div id="app">
   <b-container> 
      <b-alert :variant="status.variant" dismissible v-model="status.message" v-if="status.message">
        {{ status.message }}
      </b-alert>

      <form class="mb-3" @submit.prevent="handleSubmit" novalidate>
        <EomForm
          :schema="schema"
          v-model="model"
          ref="eomForm"
        />
        <b-btn variant="success" type="submit">submit</b-btn>
      </form>
    </b-container>
  </div>
</template>

<script>
import Vue from 'vue'
import EomForm from 'eom-form'
import schema from './data/schema.json'
  
Vue.use(EomForm)

export default {
  name: 'App',
  data () {
    return {
      status: {
        message: '',
        variant: 'success'
      },
      schema,
      model: {},
    }
  },
  methods: {
    handleSubmit () {
      this.status.message = ''
      let valid = this.$refs.eomForm.validate()

      if (!valid) {
        console.warn('invalid form')
        this.status.message = 'invalid_datas'
        this.status.variant = 'danger'
        return
      }
    }
  }
}
</script>

# Schema

{
  "firstName": {
    "component": "EomInput",
    "label": "firstname",
    "icon": "ion ion-ios-person",
    "help" : "introduce tu nombre",
    "validations": {
      "required": {
      },
      "minLength": {
        "min": 3
      }
    }
  },
  "description": {
    "component": "EomTextarea",
    "label": "description",
    "icon": "ion ion-ios-chatboxes",
    "help": "enter a long description",
    "maxlength": 50
  },
  "enabled": {
    "component": "EomCheckbox",
    "label": "enabled",
    "help": "this checkbox enable/disable something"

  },
  "checkboxGroup": {
    "component": "EomCheckboxGroup",
    "label": "checkboxGroup",
    "options": [
      { "text": "Option A", "value": "A" },
      { "text": "Option B", "value": "B" }
    ]
  },
  "radioGroup": {
    "component": "EomRadioGroup",
    "label": "radioGroup",
    "options": [
      { "text": "Option A", "value": "A" },
      { "text": "Option B", "value": "B" }
    ]
  },
  "selectOne": {
    "component": "EomSelectOne",
    "label": "selectOne",
    "options": [
      { "text": "Option A", "value": "A" },
      { "text": "Option B", "value": "B" }
    ]
  }
}

# Playground

firstname *
description *
remaining_chars 50
enabled *
checkboxGroup *
radioGroup *
selectOne
Select option
{
  "firstName": "ABC",
  "description": "",
  "enabled": 0
}
<template>
   <b-container class="p-2">
      <b-alert :variant="status.variant" dismissible v-model="status.invalid" v-if="status.message">
        {{ $t(status.message) }} 
      </b-alert>

      <form class="mb-3" @submit.prevent="handleSubmit" novalidate>
        <EomForm
          :schema="schema"
          v-model="model"
          ref="eomForm"
        />
        <b-row>
          <b-col>
            <b-btn variant="success" type="submit">submit</b-btn>
          </b-col>
        </b-row>
      </form>
 
      <pre class="language-json" v-html="model"></pre>
   </b-container>
</template>

<script>
const SCHEMA = {
  firstName: {
    component: "EomInput",
    label: "firstname",
    icon: "ion ion-ios-person",
    help : "introduce tu nombre",
    validations: {
      required: {
      },
      minLength: {
        min: 3
      }
    }
  },
  description: {
    component: "EomTextarea",
    label: "description",
    icon: "ion ion-ios-chatboxes",
    help: "enter a long description",
    maxlength: 50
  },
  enabled: {
    component: "EomCheckbox",
    label: "enabled",
    help: "this checkbox enable/disable something"
  },
  checkboxGroup: {
    component: "EomCheckboxGroup",
    label: "checkboxGroup",
    options: [
      { text: "Option A", value: "A" },
      { text: "Option B", value: "B" }
    ]
  },
  radioGroup: {
    component: "EomRadioGroup",
    label: "radioGroup",
    options: [
      { text: "Option A", value: "A" },
      { text: "Option B", value: "B" }
    ]
  },
  selectOne: {
    component: "EomSelect",
    label: "selectOne",
    options: [
      { text: "Option A", value: "A" },
      { text: "Option B", value: "B" }
    ]
  }
}

export default {
  data () {
    return {
      status: {
        message: '',
        variant: 'success',
        invalid: false
      },
      schema: SCHEMA,
      model: {
        firstName: 'ABC',
        description: '',
        enabled: 0
      },
    }
  },
  methods: {
    handleSubmit () {
      this.status.message = ''
      let valid = this.$refs.eomForm.validate()

      if (!valid) {
        console.warn('invalid form')
        this.status = {
          message:  'invalid_datas',
          variant:  'danger',
          invalid: true
        }
        return
      }
    }
  }
}
</script>

# Examples

# Select Example

selectOne
Option B
select
Option A Option B
<template>
   <b-container class="p-2">
      <b-alert :variant="status.variant" dismissible v-model="status.invalid" v-if="status.message">
        {{status.message}} 
      </b-alert>

      <form class="mb-3" @submit.prevent="handleSubmit" novalidate>
        <EomForm
          :schema="schema"
          v-model="model"
          ref="eomForm"
        />
        <b-row>
          <b-col>
            <b-btn variant="success" type="submit">submit</b-btn>
          </b-col>
        </b-row>
      </form>
    </b-container>
</template>

<script>
const SCHEMA = {
  selectOne: {
    component: "EomSelect",
    label: "selectOne",
    options: [
      {text: "Option A", value: "A"},
      {text: "Option B", value: "B"}
    ]
  },
  select: {
    component: "EomSelect",
    label: "select",
    multiple: "true",
    options: [
      {text: "Option A", value: "A"},
      {text: "Option B", value: "B"}
    ]
  }
}

export default {
  data () {
    return {
      status: {
        message: '',
        variant: 'success',
        invalid: false
      },
      schema: SCHEMA,
      model: {
        select: [ 'A', 'B' ],
	selectOne: 'B',
      },
    }
  },
  methods: {
    handleSubmit () {
      this.status.message = ''
      let valid = this.$refs.eomForm.validate()

      if (!valid) {
        console.warn('invalid form')
        this.status = {
          message:  'invalid_datas',
          variant:  'danger',
          invalid: true
        }
        return
      }
    }
  }
}
</script>

# Repeat Example

This example show how to have repeatable structure in your form. You can dynamically add/remove row.

firstname *
ipaddress *
macaddress *
{
  "firstname": "",
  "description": "",
  "enabled": 0,
  "group": [
    {
      "ipaddress": "",
      "macaddress": ""
    }
  ]
}
<template>
   <b-container class="p-2">
      <b-alert :variant="status.variant" dismissible v-model="status.invalid" v-if="status.message">
        {{status.message}} 
      </b-alert>

      <form class="mb-3" @submit.prevent="handleSubmit" novalidate>
        <EomForm
          :schema="schema"
          v-model="model"
          ref="eomForm"
        />
        <b-row class="py-2">
          <b-col>
            <b-btn variant="success" type="submit">submit</b-btn>
          </b-col>
        </b-row>
      </form>

      <pre class="language-json" v-html="model"></pre>
    </b-container>
</template>

<script>
const SCHEMA = [
  {
    component: "EomInput",
    model: "firstname",
    label: "firstname",
    icon: "ion ion-ios-person",
    help: "introduce tu nombre",
    validations: {
      required: {
      },
      minLength: {
        min: 3
      }
    }
  },
  {
    component: "EomRepeat",
    model: "group",
    schema: [
      [
        {
          component: "EomInput",
          model: "ipaddress",
          label: "ipaddress",
          autocomplete: "off",
          validations: {
            required: {},
             ip: {}
	        }
        },
        {
          component: "EomInput",
          model: "macaddress",
          label: "macaddress",
          autocomplete: "off",
          validations: {
            required: {},
            macAddress: {}
          }
        }
      ]
    ]
  }
]

export default {
  data () {
    return {
      status: {
        message: '',
        variant: 'success',
        invalid: false
      },
      schema: SCHEMA,
      model: {
        firstname: '',
        description: '',
        enabled: 0,
        group: [
          { 
            ipaddress : '',
            macaddress: ''
          }
        ]
      }
    }
  },
  methods: {
    handleSubmit () {
      this.status.message = ''
      let valid = this.$refs.eomForm.validate()

      if (!valid) {
        console.warn('invalid form')
        this.status = {
          message:  'invalid_datas',
          variant:  'danger',
          invalid: true
        }
        return
      }
    }
  }
}
</script>

# Upload Example

attachments *

drop_files_here

or_click_to_select_files
<template>
   <b-container class="p-2">
      <b-alert :variant="status.variant" dismissible v-model="status.invalid" v-if="status.message">
        {{status.message}} 
      </b-alert>

      <form class="mb-3" @submit.prevent="handleSubmit" novalidate>
        <EomForm
          :schema="schema"
          v-model="model"
          ref="eomForm"
        />
        <b-row>
          <b-col>
            <b-btn variant="success" type="submit">submit</b-btn>
          </b-col>
        </b-row>
      </form>
    </b-container>
</template>

<script>
const SCHEMA = [
  {
    component: "EomFileUpload",
    model: "attachments",
    label: "attachments",
    url: "https://eom.nixus.es:8443/api/v1/contacts/40/files",
    acceptedMimeTypes: "image/jpeg"
  }
]

export default {
  data () {
    return {
      status: {
        message: '',
        variant: 'success',
        invalid: false
      },
      schema: SCHEMA,
      model: {
        attachments: []
      }
    }
  },
  methods: {
    handleSubmit () {
      this.status.message = ''
      let valid = this.$refs.eomForm.validate()

      if (!valid) {
        console.warn('invalid form')
        this.status = {
          message:  'invalid_datas',
          variant:  'danger',
          invalid: true
        }
        return
      }
    }
  }
}
</script>

# Validators Example

spanishId *
bankAccount *
ipAddress *
ipv6Address *
macAddress *
e164PhoneNumber *
alpha *
alphaDash *
alphaNumeric *
digits *
digitsBetween *
amount *
numeric *
date *
startsWith *
endsWith *
<template>
   <b-container class="p-2">
      <b-alert :variant="status.variant" dismissible v-model="status.invalid" v-if="status.message">
        {{status.message}} 
      </b-alert>

      <form class="mb-3" @submit.prevent="handleSubmit" novalidate>
        <EomForm
          :schema="schema"
          v-model="model"
          ref="eomForm"
        />
        <b-row>
          <b-col>
            <b-btn variant="success" type="submit">submit</b-btn>
          </b-col>
        </b-row>
      </form>
    </b-container>
</template>

<script>
const SCHEMA = {
  spanishId: {
    component: "EomInput",
    label: "spanishId",
    icon: "ion ion-ios-finger-print",
    help: "",
    validations: {
      required: {
      },
      spanishId: {
      }
    }
  },
  bankAccount: {
    component: "EomInput",
    label: "bankAccount",
    icon: "ion ion-logo-euro",
    help: "",
    validations: {
      required: {
      },
      iban: {
      }
    }
  },
  ipAddress: {
    component: "EomInput",
    label: "ipAddress",
    help: "",
    validations: {
      required: {
      },
      ip: {
      }
    }
  },
  ipv6Address: {
    component: "EomInput",
    label: "ipv6Address",
    help: "",
    validations: {
      required: {
      },
      ipv6: {
      }
    }
  },
  macAddress: {
    component: "EomInput",
    label: "macAddress",
    help: "",
    validations: {
      required: {
      },
      macAddress: {
      }
    }
  },
  e164PhoneNumber: {
    component: "EomInput",
    label: "e164PhoneNumber",
    help: "",
    validations: {
      required: {
      },
      e164PhoneNumber: {
      }
    }
  },
  alpha: {
    component: "EomInput",
    label: "alpha",
    validations: {
      required: {
      },
      alpha: {
      }
    }
  },
  alphaDash: {
    component: "EomInput",
    label: "alphaDash",
    validations: {
      required: {
      },
      alphaDash: {
      }
    }
  },
  alphaNumeric: {
    component: "EomInput",
    label: "alphaNumeric",
    validations: {
      required: {
      },
      alphaNumeric: {
      }
    }
  },
  digits: {
    component: "EomInput",
    label: "digits",
    validations: {
      required: {
      },
      digits: {
      }
    }
  },
  digitsBetween: {
    component: "EomInput",
    label: "digitsBetween",
    help: "enter number between 5 and 50",
    validations: {
      required: {
      },
      digitsBetween: {
        min: 5,
        max: 50
      }
    }
  },
  amount: {
    component: "EomCurrency",
    label: "amount",
    validations: {
      required: {
      }
    }
  },
  numeric: {
    component: "EomNumeric",
    label: "numeric",
    validations: {
      required: {
      }
    }
  },
  date: {
    component: "EomDatepicker",
    label: "date",
    icon: "ion ion-ios-calendar",
    validations: {
      required: {
      } 
    }
  },
  startsWith: {
    component: "EomInput",
    label: "startsWith",
    help: "must starts with https://",
    validations: {
      required: {
      },
      startsWith: {
        needle: "https://"
      }
    }
  },
  endsWith: {
    component: "EomInput",
    label: "endsWith",
    help: "must ends with EUROS",
    validations: {
      required: {
       },
       startsWith: {
         needle: "EUROS"
       }
    }
  }
}

export default {
  data () {
    return {
      status: {
        message: '',
        variant: 'success',
        invalid: false
      },
      schema: SCHEMA,
      model: {
        spanishId: '39921948C',
        bankAccount: 'ES0504873597264184221852',
        ipAddress: '192.168.20.7',
        ipv6Address: '2001:1:2:3:4:5:6:7',
        macAddress: '0000.0000.0000',
        e164PhoneNumber: '+34968000730',
        alpha: 'abcd',
        alphaDash: 'a-b_c',
        alphaNumeric: 'abc123',
        digits: '1234',
        digitsBetween: 25,
        amount: '5.50',
        numeric: '18.36',
        date: '2020-05-07',
        startsWith: 'https://',
        endsWith: 'EUROS'
      },
    }
  },
  methods: {
    handleSubmit () {
      this.status.message = ''
      let valid = this.$refs.eomForm.validate()

      if (!valid) {
        console.warn('invalid form')
        this.status = {
          message:  'invalid_datas',
          variant:  'danger',
          invalid: true
        }
        return
      }
    }
  }
}
</script>

# Components

All components have common properties to setup things like icon or help hint

# EomInput

Create various type inputs such as: text, password, number, url, email, search, range, date and more

{
  "field": {
    "component": "EomInput",
    "label": "my label",
  }
}

# EomPasswordChecker

{
  "field": {
    "component": "EomPasswordChecker",
    "label": "my label",
  }
}

# EomCurrency

{
  "field": {
    "component": "EomCurrency",
    "label": "my label",
  }
}

# EomTextarea

{
  "field": {
    "component": "EomTextarea",
    "label": "my label",
  }
}

# EomCheckbox

{
  "field": {
    "component": "EomCheckbox",
    "label": "my label",
  }
}

# EomCheckboxGroup

{
  "field": {
    "component": "EomCheckboxGroup",
    "label": "my label",
    "options": [
      { "text": "Option A", "value": "A" },
      { "text": "Option B", "value": "B" }
    ]
  }
}

# EomRadioGroup

{
  "field": {
    "component": "EomRadioGroup",
    "label": "my label",
    "options": [
      { "text": "Option A", "value": "A" },
      { "text": "Option B", "value": "B" }
    ] 
  }
}

# EomSelect

{
  "field": {
    "component": "EomSelect",
    "label": "my label",
    "options": [
      { "text": "Option A", "value": "A" },
      { "text": "Option B", "value": "B" }
    ]
  }
}

# EomDatepicker

{
  "field": {
    "component": "EomFileUpload",
    "label": "my label",
  }
}

# EomFileUpload

{
  "field": {
    "component": "EomFileUpload",
    "label": "my label",
  }
}

# EomEditor

{
  "field": {
    "component": "EomEditor",
    "label": "my label",
  }
}

# Validators

This library comes with over 20 built in validators.

  • minLength:
  • alpha:
  • alphaNumeric:
  • alphaDash:
  • date:
  • beforeDate:
  • beforeOrIgualDate:
  • digits:
  • digitsBetween:
  • endsWith:
  • startsWith:
  • email:
  • url:
  • sameAs:
  • regex:
  • phoneNumber:
  • spanishID: validate spanish personal identity number (CIF,NIF,NIE)
  • iban: validate an IBAN bank account number
  • e164Number: validate a E.164 phone number (e.g. +34968123456)
  • macAddress: validate a mac address (e.g. af:af:af:af:af:af)
  • ip: validate an ipv4 address (e.g. 192.168.1.1)
  • ipV6: validate an ipv6 address (e.g. 2001:1:2:3:4:5:6:7)

# Contribution

We need hands. Please send pull requests improving the usage and fixing bugs, improving documentation and providing better examples, or providing some testing.