loading... Pacem JS

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.

Form glue

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.

Form nesting

<pacem-form> elements can be nested so that validity and dirtiness may propagate upwards up to the containing form.

Form (auto)generation

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 } } ] } ] }

Metadata

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/or display.ui, unless it is provided as a factory function.

Managed values:

dataType

The enhanced type of the editing property.

It overrides type.

Managed values:

display.ui:

An additional UI hint for the editing property.

It overrides both type and dataType.

{ "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 } } ] } ] }

Behavior

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:

Styling

Form fields appearance can be tweaked using the css classes embedded in the css theme:

Minimal

When .form-minimal is applied to the whole form or .field-minimal is applied to a specific field:

Outline

When .field-outline is applied to a specific field:

Labels

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, ...).

Stretching

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" } } ] }

Examples

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" } } ] } ] }