@molgenis/molgenis-ui-form
v5.9.4
Published
Library for generating HTML web forms
Downloads
56
Readme
molgenis-ui-form
Library for generating HTML web forms
Changelog
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)
Usage
Install
# Add library using yarn.
yarn add @molgenis/molgenis-ui-form
Use
<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.
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
}
}
addOptionRequest event
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 theoption
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 value
Options
The 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. |
Form specifications
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'
}
Type support
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 | | email | 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 |
Option field example
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
Field group example
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'
}
Range
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
}
}
Required, visible, and validate
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
}
]
Unique field value validation
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 message localization
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.
message keys and defaults
| 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' |
Entity mapper options
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 areUPDATE
andCREATE
) if set toCREATE
readonly attributes map to writable fields, defaults toCREATE
Invalid validation expression handling
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
.
Description fields
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
Development
The general guidelines and setup of the development environment are described here.
Build setup
# 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
How to commit
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"
How to publish
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/.
Tests
To develop tests please acknowledge the following guidelines.
End-to-End test
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'
Integration with 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
.