Forms are pivotal in Pacem JS. Since nobody loves implementing forms, Pacem JS aims to include functionalities for as may use case as possible. More features are yet to come.
We'll skip standard static laydown of forms and fields, since you can grasp the basics by lurking into the code of the following - hopefully more interesting - examples.
Given an entity to be edited with a form, just setup the binding between entity and form via specific attribute.
The named PacemModelElement
instances inside the form will figure out their respective property bindings:
This default behavior can be disabled by adding the attribute
autobind="off"
to the pacem input field.
<pacem-form>
elements can be nested so that validity and dirtiness may propagate upwards up to the containing form.
Forms can be autogenerated via proper metadata.
"Proper" metadata are defined below (simplified version):
type DisplayMetadata = {
name: string, description?: string, short?: string, watermark?: string, null?: string, ui?: string, format?: string,
css?: { [name: string]: string },
cssClass?: string[],
}
type ValidatorMetadata = { type: string, errorMessage: string, params?: any };
type PropertyMetadata = {
prop: string,
type: 'object' | 'array' | string | PropertyMetadataFieldFactory,
display?: DisplayMetadata,
extra?: any,
isReadOnly?: boolean,
dataType?: string,
validators?: ValidatorMetadata[],
commands?: CommandMetadata[],
props?: PropertyMetadata[]
};
type TypeMetadata = {
display?: DisplayMetadata,
props: PropertyMetadata[],
type?: 'object' | 'array'
}
Metadata-generated forms are convenient in many ways:
Here's the mise en place:
{ "display": { "cssClass": ["pacem-grid", "grid-tiny-colgap", "grid-no-rowgap", "pacem-margin", "margin-bottom-0"] }, "props": [ { "prop": "Username", "type": "text", "display": { "watermark": "your username here...", "cssClass": ["pacem-cell"] }, "commands": [ { "name": "generate", "icon": "auto_awesome", "tooltip": "auto generate", "cssClass": ["button"], "prepend": false } ], "validators": [ { "type": "required", "errorMessage": "field is required" }, { "type": "email", "errorMessage": "field is not well formed", "params": { "pattern": "^[\w\.-]+$" } }, { "type": "length", "errorMessage": "length must be between 3 and 20", "params": { "max": 20, "min": 3 } } ] }, { "prop": "Password", "type": "password", "display": { "cssClass": ["pacem-cell", "cols-6", "cols-xs-12"] }, "validators": [ { "type": "required", "errorMessage": "field is required" } ] }, { "prop": "PwdRepeat", "type": "password", "display": { "name": "Password (confirm)", "cssClass": ["pacem-cell", "cols-6", "cols-xs-12"] }, "extra": { "dependsOn": [{ "prop": "Password"}]}, "commands": [ { "name": "copy", "icon": "file_copy", "tooltip": "copy to clipboard", "cssClass": ["button"], "dependsOnValue": true }, { "name": "go", "icon": "keyboard_arrow_right", "tooltip": "dummy", "cssClass": ["button"], "prepend": false } ], "validators": [ { "type": "compare", "errorMessage": "the two password fields are not equal", "params": { "to": "Password", "operator": "equal" } }, { "type": "length", "errorMessage": "password must be at least 8 chars long", "params": { "min": 8 } } ] }, { "prop": "Height", "type": "float", "display": { "name": "Height (m)", "cssClass": ["pacem-cell"] }, "validators": [ { "type": "range", "errorMessage": "height must be between 1.0m and 2.5m", "params": { "max": 2.5, "min": 1.0 } } ] } ] }
Here's a more detailed explanation about the form-generating metadata:
prop
The very name of the editing property.
display.name
The readable name of the editing property, displayed as the label on the editing field.
Falls back to the raw prop
name when not provided.
type
The base type of the editing property. It ultimately decides the editing field that has to be rendered.
It may be overridden by
dataType
and/ordisplay.ui
, unless it is provided as a factory function.
Managed values:
boolean
: renders a checkbox;byte
: renders a numeric field whose value is constrained between 0 and 255.int16
, short
, int32
, int
, integer
, int64
, long
: renders a numeric field with step 1.single
, float
, double
, decimal
, number
: renders a numeric field with step any.array
, object
: renders a child form using the provided props
metadata.dataType
The enhanced type of the editing property.
It overrides
type
.
Managed values:
upload
: renders a binary editing field.html
: renders an html rich text editor.enumeration
: renders a radio-list having extra.enum
as a datasource, extra.enum
should provide an array of elements of type { caption: string, value: any }
.password
: renders a password box.emailaddress
: renders an email box.color
: renders a color picker.datetime
: renders a datetime picker (minute precision).date
: renders a date picker.url
: renders an URL editor.phonenumber
: renders an phone number editor.multilinetext
: renders a multi-line textarea.markdown
: renders a markdown editor.latlng
: renders a geo-coordinates picker.percent
, currency
: renders a numeric field with relevant formatting (in its readonly fashion).display.ui
:An additional UI hint for the editing property.
It overrides both
type
anddataType
.
oneToMany
(case-sensitive): renders a select field having either a extra.sourceUrl
to fetch data from or a straight extra.source
as a datasource array.extra.textProperty
and extra.valueProperty
should be provided for complex items in order to properly render labels and bind values.extra.disabledProperty
might help when option disabling is needed.manyToMany
(case-sensitive): renders a checkbox-list. Same considerations about datasource and property references as for oneToMany
.suggest
: renders a suggest field that eases the pick of a value by allowinf dynamic filtering.tags
: renders a tags field.{ "display": { "cssClass": ["pacem-grid", "grid-nospace", "grid-no-rowgap", "form-minimal", "form-xs-nolabels", "pacem-pad", "pad-left-1"] }, "props": [ { "prop": "type", "dataType": "enumeration", "type": "string", "display": { "cssClass": ["pacem-cell", "field-outline"], "name": "Switch messaging mode" }, "extra": { "enum": [{ "value": 1, "caption": "e-mail" }, { "value": 2, "caption": "texting" }, { "value": 3, "caption": "mail" }] }, "validators": [ { "type": "required", "errorMessage": "required field" } ] }, { "prop": "body", "dataType": "html", "type": "string", "display": { "cssClass": ["pacem-cell", "field-outline"], "name": "Message Body" }, "extra": { "dependsOn": [{ "prop": "type", "value": 1, "hide": true }] }, "validators": [ { "type": "required", "errorMessage": "required field" } ] }, { "prop": "content", "type": "string", "dataType": "multilinetext", "display": { "cssClass": ["pacem-cell", "field-outline"], "name": "SMS Message" }, "extra": { "dependsOn": [{ "prop": "type", "value": 2, "hide": true }] }, "validators": [ { "type": "required", "errorMessage": "required field" }, { "type": "length", "errorMessage": "Message too long", "params": { "max": 140 } } ] } ] }
Form usually behaves proactively to the user: by default it hints the invalid fields from the beginning and - when autogenerated - binds the submit and reset buttons
to the current status of the form (validity, dirtiness, ...).
This default behavior can be changed by changing the value of the following properties:
suddenValidation
(default true): when set to false, the submit button will always be shown (if an action url is provided) and it will eventually trigger the first overall validation.resettable
(default true): when set to false, the reset button will always be hidden.Form fields appearance can be tweaked using the css classes embedded in the css theme:
When .form-minimal
is applied to the whole form or .field-minimal
is applied to a specific field:
When .field-outline
is applied to a specific field:
When .form-nolabels
is applied to the whole form or .field-nolabel
is applied to a specific field:
Label visibility can depend on screen size: .form-xs-nolabels
, .field-xs-nolabel
, ..., .form-4k-nolabels
, .field-4k-nolabel
.
Label visibility can be punctually overridden by setting .field-label
css class on a specific field (screen size dependencies also included: .field-xs-label
, ...).
By default, input fields are constrained to a specific width only for large screens.
By setting the .field-stretch
css class on a specific field, yo can break this rule.
{ "display": { "cssClass": ["pacem-grid", "grid-nospace", "grid-no-rowgap", "form-minimal", "form-xs-nolabels", "pacem-pad", "pad-left-1"], "name": "Sign In" }, "props": [ { "prop": "email", "dataType": "emailaddress", "type": "string", "display": { "cssClass": ["pacem-cell", "field-outline"], "name": "E-mail", "watermark": "name@email.com..." }, "validators": [ { "type": "required", "errorMessage": "required field" }, { "type": "email", "errorMessage": "malformed field" } ] }, { "prop": "password", "dataType": "password", "type": "string", "display": { "cssClass": ["pacem-cell", "field-outline"], "name": "Password", "watermark": "password..." }, "validators": [ { "type": "required", "errorMessage": "required field" } ] }, { "prop": "rememberMe", "type": "boolean", "display": { "cssClass": ["pacem-cell", "field-nolabel"], "watermark": "Remember me on this device" } } ] }
In the following autogenerated form, a summa of rendering possibilities are randomly shown alongside with validation and behaviors.
Please notice the tooltips!
{
"display": { "cssClass": ["pacem-grid", "grid-nospace", "form-minimal", "form-xs-nolabels", "pacem-pad", "pad-left-1"], "name": "Sign In" },
"props": [
{
"prop": "email", "dataType": "emailaddress", "type": "string",
"display": { "cssClass": ["pacem-cell", "field-outline", "field-stretch"], "name": "E-mail", "watermark": "name@email.com..." },
"validators": [
{ "type": "required", "errorMessage": "required field" },
{ "type": "email", "errorMessage": "malformed field" }
]
},
{
"prop": "password", "dataType": "password", "type": "string",
"display": { "cssClass": ["pacem-cell", "cols-lg-6", "field-outline"], "name": "Password", "watermark": "password..." },
"validators": [
{ "type": "required", "errorMessage": "required field" },
{ "type": "regex", "errorMessage": "weak password", "params": { "pattern": "^(?=.[a-z])(?=.[A-Z])(?=.*[0-9])(?=.{8,})" } }
]
},
{
"prop": "confirmPassword", "dataType": "password", "type": "string",
"display": { "cssClass": ["pacem-cell", "cols-lg-6", "field-outline"], "name": "Password", "watermark": "confirm password...", "description": "Rewrite the password
here \n(To be sure you typed it correctly)" },
"extra": { "tooltip": { "type": "markdown", "position": "top", "align": "start" } },
"validators": [
{ "type": "required", "errorMessage": "required field" },
{ "type": "compare", "errorMessage": "password mismatch", "params": { "to": "password" } }
]
},
{
"prop": "birthdate", "type": "datetime",
"display": { "cssClass": ["pacem-cell", "cols-lg-6", "cols-hd-4", "field-full"] },
"extra": { "format": { "weekday": "short", "year": "numeric", "month": "long", "day": "numeric", "minute": "2-digit", "hour": "2-digit"} }
},
{
"prop": "graduation", "type": "date",
"display": { "cssClass": ["pacem-cell", "cols-lg-6", "cols-hd-4", "field-full"] },
"validators": [
{ "type": "compare", "errorMessage": "dates mismatch", "params": { "to": "birthdate", "operator": "greater" } }
]
},
{
"prop": "anniversary", "type": "date",
"display": { "cssClass": ["pacem-cell", "cols-hd-4", "field-full"] },
"validators": [
{ "type": "compare", "errorMessage": "dates mismatch", "params": { "to": "birthdate", "operator": "greater" } }
]
}
]
}
To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.