Library for generating HTML web forms
Details changes for each release generated in the CHANGELOG.md. You can view the changes on GitHub as well (https://github.com/molgenis/molgenis-ui-form/releases)
# Add library using yarn.
yarn add @molgenis/molgenis-ui-form
<template>
<form-component
id="example-form"
:formFields="formFields"
:initialFormData="formData"
:formState="formState"
:options="options"
@valueChange="onValueChanged"
@addOptionRequest="handleAddOptionRequest"
>
</form-component>
</template>
<script>
// Import form component
import { FormComponent } from '@molgenis/molgenis-ui-form'
// Import EntityToFormMapper
import { EntityToFormMapper } from '@molgenis/molgenis-ui-form'
// Import all form component styles
import '../node_modules/@molgenis/molgenis-ui-form/dist/static/css/molgenis-ui-form.css'
export default {
name: 'ExampleComponent',
data() {
return {
formFields: [],
formData: {},
formState: {},
options: {
showEyeButton: true
}
}
},
methods: {
handleAddOptionRequest(addNewItemFunction) {
// Handle the request to add a new item here
const item = {
id: 'new_item',
label: 'New Item',
value: 'new_item'
}
addNewItemFunction(item)
},
onValueChanged(formData) {
// Do something with the updated formData
console.log(formData)
}
},
created() {
// Generate a form from a MOLGENIS v2 API response
// Or create fields based on the specs (Form specifications)
const form = EntityToFormMapper.generateForm(metadata, items[0])
this.formFields = form.formFields
this.formData = form.formData
},
components: {
FormComponent
}
}
</script>
Note: Whatever you pass to the FormComponent as formData object,
the FormComponent makes a local copy with Object.assign({}, formData)
.
If you want to react to data input, use the @valueChanged event.
When data in the form is changed, the form fires a valueChanged
event.
This event exposes two arguments, formData
and isFormValid
.
formData
is a key value object, where the field ID is the key, and the data filled in by the user is the value.
isFormValid
is a boolean telling you if there are any invalid fields in the form.
An example handler is shown below
methods: {
onValueChanged (formData) {
console.log(formData) // all the data currently in the form
}
}
To allow the user to add new options to a select list the handleAddOptionRequest
should be a function with the following properties:
completedFunction
a callback function that should be called passing the option
to be added.event
the original event triggering the request.data
object with form field state data.The option
object passed to the completedFunction
should at least have the following fields
id
unique identifierlabel
the label shown to the uservalue
the form valueThe FormComponent object can be configured via an options property. If no options object is supplied the defaults are used.
Option name | Default | Description |
---|---|---|
showEyeButton | True | Toggle the visibility of the Eye button. |
allowAddingOptions | False | When set to true users are show a interface item to add options to a (multi)select. |
inputDebounceTime | 500 | Time (in milliseconds) between input updates needs to pass before update event is fired. |
Example on how to create fields and data objects for using the forms.
const fields = [
{
type: 'text',
id: 'example-text-field',
label: 'Example text field',
description: 'This field shows how to create a form field from scratch',
required: (formData) => true,
disabled: false,
readOnly: false,
visible: (formData) => true,
validate: (formData) => true
}
]
const data = {
'example-text-field': 'example value'
}
We support most HTML input types like number, text, and email. Below is a list of supported types.
type | renders |
---|---|
radios | A list of radio buttons |
single-select | A Vue select dropdown which supports asynchronous and synchronous option lists |
multi-select | A Vue Multiselect dropdown which supports asynchronous and synchronous option lists |
number | A HTML5 number input |
integer | A HTML5 number input, with step size set to 1 and may not include a fraction and must lie between --2147483648 and 2147483647 |
long | A HTML5 number input, Long may not include a fraction and should fit into javascript number type. |
decimal | A HTML5 number input, may include a fraction |
text-area | A textarea HTML element |
date | A Vue Flatpickr Date component |
date-time | A Vue Flatpickr Date component with 'enableTime = true' |
checkboxes | A list of checkboxes |
text | A HTML5 text input |
hyperlink | A HTML5 text, only valid uri is allowed |
A HTML5 text email | |
password | A HTML5 password input |
file | A HTML5 file input |
field-group | A type that is used to nest other inputs |
Fields that render lists of options like radio buttons, checkboxes, and select dropdowns have an additional option
parameter.
This option parameter should always contain a function returning a promise.
This makes it usable for both synchronous and asynchronous rendering of option lists.
const fields = [
{
type: 'field-group',
id: 'example-field-group',
label: 'Example field group',
description: 'This field shows how to create a group of fields',
required: (formData) => true,
disabled: false,
readOnly: false,
visible: (formData) => true,
validate: (formData) => true,
options: () => {
const options = [
{
id: '1',
label: 'Option 1',
value: '1'
},
{
id: '2',
label: 'Option 2',
value: '2'
}
]
return Promise.resolve(options)
},
isAddOptionAllowed: () => Promise.resolve(true)
}
]
The isAddOptionAllowed
function indicated if the user should be allowed to add options
A field group can be used to group a set of fields.
You can specify a list of fields under a field-group via the children
parameter.
const fields = [
{
type: 'field-group',
id: 'example-field-group',
label: 'Example field group',
description: 'This field shows how to create a group of fields',
required: (formData) => true,
disabled: false,
readOnly: false,
visible: (formData) => true,
validate: (formData) => true,
children: [
{
type: 'text',
id: 'example-field-group',
label: 'Example text field',
description: 'This field shows how to create a form field from scratch',
required: (formData) => true,
disabled: false,
readOnly: false,
visible: (formData) => true,
validate: (formData) => true
}
]
}
]
// Note that the field group itself does not have data
const data = {
'example-text-field': 'example value'
}
A number fields valid input range can be restricted by supplying a range object
The range object can have a min
property, a max
property or both.
for example
{
id: 'example-number-with-range',
type: 'integer' | 'long',
range: {
min: 0,
max: 13
}
}
As you might have noticed in the above examples, required, visible, and validate are functions returning a boolean. The reason for this is that you might want to validate a field based on the input of another field.
Or show / hide a field once another field has a certain value.
const fields = [
{
type: 'text',
id: 'example-text-field',
label: 'Example text field',
description: 'This field controls the visibility of example-number-field',
required: (formData) => true,
disabled: false,
readOnly: false,
visible: (formData) => true,
validate: (formData) => true
},
{
type: 'integer',
id: 'example-integer-field',
label: 'Example integer field',
description:
'This field is shown if "example-text-field" contains the text "show"',
required: (formData) => true,
disabled: false,
readOnly: false,
visible: (formData) => {
return formData['example-text-field'] === 'show'
},
validate: (formData) => true
}
]
Fields of type string, number, integer, long, decimal or radio may include a function that validated value uniqueness. The function should return a promise that resolves to a boolean indicating if the value is unique in some context.
Example:
unique: (proposedValue, [context]) => {
return new Promise((resolve, reject) => {
api.get('some-api-call-that-checks-proposed-value').then((result) => {
resolve(result)
})
})
}
Validation messages may be localized via the use of the @molgenis/molgenis-i18n-js
plugin.
Usage:
Vue.use(i18n, {
lng: 'en',
fallbackLng: 'en',
namespace: ['some-namespace', ui-form],
callback () {
/* eslint-disable no-new */
new Vue({
el: '#form-demo',
template: '<FormDemo/>',
components: { FormDemo }
})
}
})
If no 'ui-form' namespace is set on the supplied Vue instance the default (English) messages are used.
Key | Default message | Additional Info |
---|---|---|
ui-form:form_required_field | 'This field is required' | |
ui-form:form_validation_failed | 'Validation failed' | |
ui-form:form_not_a_valid_number | 'Not a valid number' | |
ui-form:form_not_a_valid_hyperlink | 'Not a valid Hyperlink' | |
ui-form:form_not_a_valid_email | 'Not a valid email' | |
ui-form:form_not_within_range | 'Value is outside of range' | min, max is added as: ' ($min - $max)' |
ui-form:form_below_min_value | 'Value is below allowed value' | min value is added as: ' $min' |
ui-form:form_above_max_value | 'Value is above allowed value | max value is added as: ' $max' |
The EntityToFormMapper.generateForm
function takes a optional options
param.
The options param is a object that can contain the following properties:
booleanLabels
Optional Object used to set labels for boolean type fields, can be use in combination with i18n plugin to translate boolean labels.showNonVisibleAttributes
Optional boolean if set to true maps non visible attributes to visible field, defaults to falsemapperMode
Optional string (valid modes are UPDATE
and CREATE
) if set to CREATE
readonly attributes map to writable fields, defaults to CREATE
Erroneous validation expressions in the visibleExpression
and nullableExpression
metadata fields will fallback to the respective
visible
and nillable
values. A broken validationExpression
will fall back to being true
.
The description will automatically make URL's clickable.
You can create a (read more) button by using an enter
in the description field, which will display the remaining text after the enter.
You are able to create a information tooltip by using the following tag inside of the description field
{i}My information icon tooltip{/i}
Break lines with {br}
for example a description like this:
{br}• list item 1{br}• list item 2{br}• list item 3{br}
Will render a list like this:
• List item 1
• List item 2
• List item 3
The general guidelines and setup of the development environment are described here.
# install dependencies
yarn install
# serve with hot reload at localhost:8080
yarn run dev
# build for production with minification
yarn run build
# build for production and view the bundle analyzer report
yarn run build --report
# run unit tests
yarn run unit
# run continuous unit tests
yarn run unitwatch
# run e2e tests
yarn run e2e
# run all tests
yarn test
We use conventional commits to generate changelogs and release notes. Please check: https://www.conventionalcommits.org/
Example
git commit file.ext -m "fix(file.ext): fixes something"
Each time a PR is merged a release will be done to NPM. The CHANGELOG.md and GitHub release will be ammended.
The version of the package is based upon convential commits. Check: http://commitizen.github.io/cz-cli/.
To develop tests please acknowledge the following guidelines.
Please make sure you add the name of the specific test in the test. This is needed to see the test-name in Saucelabs. Example
browser.options.desiredCapabilities.name = 'Example testname'
vue-form
We use the vue-form
library for validation.
The component tree mingles elements from vue-form
and molgenis-ui-form
like this:
<FormComponent :formState :initialFormData>
<VueForm :state="formState">
<FormFieldComponent :formState :formData :field>
<TypedFieldComponent v-model="formData[field.id]">
<Validate :state :debounce>
<input v-model="localValue">
<FormFieldMessages>
The TypedFieldComponent
creates a localValue
data element and
binds it to a type-specific input inside a Validate
component.
The TypedFieldComponent
fires @Input
events whenever localValue
changes.
These @Input
events are bound to formData[field.id]
vue-form
debounces the validation until input ceases.
The FormFieldComponent
listens for formState[field.id].$pending
to become false
to make
sure that validation is done and the fieldState
is up to date.
Then it emits a @dataChange
event to the FormComponent
and the FormComponent
emits @valueChange
.