Simple Form Manager V2 (SFMv2) is a javascript class you can use to manage your html forms. It can be used with Angular, React, Vue or any other javascript library for framework. Good form management requires an an tremendous amount boilerplate and redundant code to manage state, and validate user input. Most solutions are complicated, highly opinionated and tend to be framework.
SFMv2 can be usued with any one of the dozens of UI components available to you. Simple Form Manager does not takeover your forms or shroud your forms in obscurity so you do not understand what is happening to your data, SFMv2 is lightweight, simple and is easy to reason about. SFMv2 internal objects are fully exposed to you, the developer, so you can see what is going with your data.
SFMv2 is a complete re-write of the orginial Simple Form Manager and has a completely different interface. Unlike the original version of Simple Form Manager, this version does not require you to put a wrapper around your components for it to work. With Simple Form Manager V2 you just wire your components it, and it works!
SFM and SFMv2 are inspired by Vuelidate, the excellent form validation system written for Vue.
npm install simple-form-manager-v2
or
yarn add simple-form-manager-v2
A full description of these properties and methods can be found futher down in this document.
Instantiate Simple Form Manager V2
import FM from 'simple-form-manager-v2'
import myFormSchema from './src/form-schemas/myFormSchema.js'
const fm = new FM(myFieldSchema)
But what is myFormSchema in the code sample above you ask. It is a javascript object describing your form and the validation rules for each field. See below for how to structure your form schema and how to write a validation function.
Simple Form Manager V2 will work with any framework. The example I use here is for a VueJs use case.
// userFormSchema.js
import { required } from 'vuelidate/lib/validators'
export default {
userName: {
required: {
validator: required,
errorMessage: 'User Name is required'
}
}
}
// userForm.vue
<template>
<form @submit.prevent="onSubmit">
<!--
We will show field errors here...
Only show if the field has been 'touched', i.e. received and lost focus
-->
<div v-if="(!fm.fields.userName.valid && fm.fields.userName.touched)">
<div>{{ fm.fields.userName.errorMessage }}</div>
</div>
<!--
Each field in your form is set-up as follows
-->
<input
@blur="fm.onBlur('userName')"
v-model="fm.fields.userName.value"
type="text"
/>
<!--
Note: We can disable the submit button if there is an error
in one or more of the fields or if the form values are unchanged
-->
<input
type="submit"
value="Submit"
:disabled="!(fm.form.dirty && fm.form.valid)"
>
</form>
</template>
<script>
import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
import FM from './assets/CFormManagerV2'
import Schema from './assets/userFormSchema'
export default defineComponent({
name: 'user-form',
setup() {
// Instantiate Form Manager
// We are using Vue3 so this is done inside a ref()
const fm = ref(new FM(Schema))
// When page is mounted set-up the form
onMounted(() => {
// Initial value of userName field
fm.value.fields.userName.value = 'Joe Blow'
// CRITICAL - start form manager AFTER field
// values have been initialized
fm.value.start()
})
// CRITICAL - to prevent memory leaks,
// stop form manager when page is unMounted
onUnmounted(() => {
fm.value.stop()
})
const onSubmit = () => {
// Notice a nice package of your form data is available
// fm.data
console.log('Submit: ', fm.value.data)
fm.value.resetForm()
}
return {
fm, // We must make fm available to the form
onSubmit
}
}
});
</script>
In the Userage sction above, we had a form schema name myFormSchema. Here is a simple example of a form schema.
// I am getting my validator functions here from npm package
// vuelidate. You can write your own or find others on npm.
import { required, email } from 'vuelidate/lib/validators'
export default {
// the form descibed in this file had three fields:
// - userId
// - userName
// - email
userId: {}, // no validation rules, in the interface the id is likely read-only
username: {
required: { // validation rule for username
validator: required,
errorMessage: 'Username is required'
}
},
// email is the other field variable name in your form
email: {
required: {
validator: required, // Must me a function, see below for details
errorMessage: 'Email is required' // Must be a string
},
validEmail: { // validation rule #2 for email
validator: email,
errorMessage: 'Email is not a proper email format'
}
validEmail: { // validation rule #2 for email
validator: maxLength(100),
errorMessage: 'Email may not exceed 100 characters'
}
}
}
Notice that each of the fields usermame and email in the form schema above has node(s) below them - these are validation rule nodes. You may have as many validation rules (or none at all) for each field as you like and you may give them any name that you like.
The only requirement for each validation rule is that each validation rule node must have two sub-nodes: validator & errorMessage. The validator node must be assigned a function, this fn is used to validated the value assigned to field. The errorMessage node is assigned a string that your form can you to display to the user is the validation rule fails.
That's it! To summarize: a form schema consists of the follow structure:
field-node(s)
validation-rule-node(s)
validator: function
errorMessage: string
A validator function takes one of the following two forms:
// Validation rule with no parameter (i.e. required)
fn(valueToBeValidated) => { return boolean }
// Validation rule with one, or more parameters (i.e. maxLength(100))
fn(valueToBeValidated => fn2 => (parameters...) => { return boolean })
You may use write your own validaiton function or you can import 3rd-party validators from npm. An excellent libray I use comes from vuelidate.
import FM from 'simple-form-manager-v2'
import myFormSchema from './src/form-schemas/loginFormSchema.js'
const fm = new FM(loginFormSchema.js)
// starts tracking user input with a frequency of 1 second
fm.start(1000)
// starts tracking user input with a frequency of 1 second
fm.stop()
// change the value of the 'lastName' field to and empty string (i.e. '')
fm.setTouched('lastName', '')
let person = {
FirstName: 'Mary',
MiddleInitial: 'L',
LastName: 'Henderson'
}
// sets the values of the fields: FirstName, MiddleInitial & LastName
fm.setValues(person)
// sets the touched value for the password field to true
fm.setValue('password')
let country = {
countryCode: 'CH',
countryName: 'Switzerland',
capital: 'Bern'
}
// change the objectValue of the 'countryCode' field to country
fm.setUpdateModelValue('countryCode', country)
// sets the touched value for the password field to true
fm.onBlur('password')
// change the value of the 'lastName' field to and empty string (i.e. '')
fm.onUpdateValue('lastName', '')
const formScheme = {
password: {
required: {
validator: required,
message: 'Password is required'
}
},
confirmationPassword: {
matchesPassword: {
validator: null, // No validator, this is handled outside of Form Manager
message: 'Password and Confirmation Password do not match'
}
}
}
// change the 'valid' status of the 'confirmationPassword' to false
fm.setFieldStatus('confirmationPassword', 'matchesPassword', false)
// change the 'valid' status of the 'confirmationPassword' to false
fm.setFieldStatus('confirmationPassword', false, 'Password and Confirmation Password do not match')
// change the value of the 'lastName' field to and empty string (i.e. '')
fm.onUpdateModelValue('countryCode', {
countryCode: 'CH',
countryName: 'Switzerland',
capital: 'Bern'
})
Use this function to true on and off validation rules established in the Form Schema. If value is not provided, the rule is toggled, otherwise the rule is set to the value provided.
// sets the 'required' validation rule for the 'socialSecurityNumber'
// field to false
fm.toggleValidationNode('socialSecurityNumber', 'required', false)
// return true if the 'password' field is both invalid and touched
fm.showFieldError('password')
//
// Values before resetting the form
//
// Field values
console.log(fm.fields)
// returns
{
"userName": {
"dirty": true,
"touched": true,
"value": "janejohnson",
"originalValue": "sallysmith",
"valid": true,
"errorMessage": ""
},
"phoneNumber": {
"dirty": true,
"touched": true,
"value": "999QQQQQQQ",
"originalValue": "888-999-1234",
"valid": false,
"errorMessage": "Not a valid phone number"
}
}
// Form values
console.log(fm.form)
// returns
{
"dirty": true,
"valid": false,
"touched": true
}
//
// Values after resetting the form
//
fm.resetField('userName')
console.log(fm.fields.userName)
// Field values
console.log(fm.fields)
// returns
{
"userName": {
"dirty": false,
"touched": false,
"value": "sallysmith",
"originalValue": "sallysmith",
"valid": true,
"errorMessage": ""
},
"phoneNumber": {
"dirty": false,
"touched": false,
"value": "888-999-1234",
"originalValue": "888-999-1234",
"valid": false,
"errorMessage": "Not a valid phone number"
}
}
// Form values
console.log(fm.form)
// returns
{
"dirty": false,
"valid": false,
"touched": false
}
//
// Values before resetting the form
//
// Field values
console.log(fm.fields)
// returns
{
"userName": {
"dirty": true,
"touched": true,
"value": "janejohnson",
"originalValue": "sallysmith",
"valid": true,
"errorMessage": ""
},
"phoneNumber": {
"dirty": true,
"touched": true,
"value": "999QQQQQQQ",
"originalValue": "888-999-1234",
"valid": false,
"errorMessage": "Not a valid phone number"
}
}
// Form values
console.log(fm.form)
// returns
{
"dirty": true,
"valid": false,
"touched": true
}
//
// Values after resetting the form
//
fm.resetField('userName')
console.log(fm.fields.userName)
// Field values
console.log(fm.fields)
// returns
{
"userName": {
"dirty": false,
"touched": false,
"value": "janejohnson",
"originalValue": "janejohnson",
"valid": true,
"errorMessage": ""
},
"phoneNumber": {
"dirty": false,
"touched": false,
"value": "999QQQQQQQ",
"originalValue": "999QQQQQQQ",
"valid": false,
"errorMessage": "Not a valid phone number"
}
}
// Form values
console.log(fm.form)
// returns
{
"dirty": false,
"valid": false,
"touched": false
}
console.log(fm.fields.userName)
// returns
{
"dirty": true,
"touched": true,
"value": "janejohnsonaaaaaaaaaaaaaaaaaaaaaa",
"originalValue": "",
"valid": false,
"errorMessage": "User Name exceeds 15 charcters"
}
fm.resetField('userName')
console.log(fm.fields.userName)
// returns
{
"dirty": false,
"touched": false,
"value": "janejohnsonaaaaaaaaaaaaaaaaaaaaaa",
"originalValue": "janejohnsonaaaaaaaaaaaaaaaaaaaaaa",
"valid": false,
"errorMessage": "User Name exceeds 15 charcters"
}
fm.start()
console.log(fm.running)
// returns
true
// Read Example
console.log(fm.fields)
// returns
{
"userName": {
"dirty": true,
"touched": true,
"value": "jdoe",
"originalValue": "",
"valid": true,
"errorMessage": ""
},
"password": {
"dirty": true,
"touched": true,
"value": "hello1",
"originalValue": "",
"valid": false,
"errorMessage": "Passwords must be at least 8 charcters and consist of at least 1 special charcter "
}
}
// Write Example
fm.fields.userName.value = 'johnDoe'
fm.fields.phoneNumber.value = '999-999-1212'
fm.start()
<!--
Vue JS: Databinding to field object 'fm.fields.userName.value'
-->
<input
@blur="fm.onBlur('userName')"
v-model="fm.fields.userName.value"
type="text"
/>
<!--
React JS: Databinding to field object 'fm.fields.userName.value'
-->
handleChange = (e) => {
/// ...handle update
},
handleBlur = (e) => {
/// ...handle update
},
render(){
return (
<input
value="{this.state.fm.fields.userName.value"
onChange={this.handleChange}
onBlur={this.handleBlur}
type="text"
/>
)
}
Though these properties are read/write, with the exception of orginalValue, value, Form Manager will overwrite your changes (if it is running) with the tickSpeed provided at start(). As such, you will only ever want to directly update value
console.log(fm.form)
///returns (example)
{
"dirty": false,
"valid": true,
"touched": true
}
// ********************
// **** Example #1 ****
// ********************
console.log(fm.form)
// returns
{
"dirty": false,
"valid": true,
"touched": true
}
console.log(fm.formSubmittable)
// returns
false
// ********************
// **** Example #2 ****
// ********************
console.log(fm.form)
// returns
{
"dirty": true,
"valid": true,
"touched": true
}
console.log(fm.formSubmittable)
// returns
true
import FM from 'simple-form-manager-v2'
import myFormSchema from './src/form-schemas/myFormSchema.js'
const fm = new FM(myFieldSchema)
console.log(fm.scheme)
//returns
{
"userName": {
"required": {
"errorMessage": "User Name is required",
"isActive": true
}
}
}
fm.toggleValidationNode('userName', 'required', false)
//returns
{
"userName": {
"required": {
"errorMessage": "User Name is required",
"isActive": false
}
}
}
import FM from 'simple-form-manager-v2'
import myFormSchema from './src/form-schemas/myFormSchema.js'
const fm = new FM(myFieldSchema)
fm.fields.userName = 'sallysmith'
fm.fields.phone = '000-888-9999'
fm.fields.income = '999999'
console.log(fm.data)
{
"userName": "sallysmith",
"phone": "000-888-9999",
"income": "999999",
"age": null
}
This is made available primarily for debugging your form code