Appearance
Forms
An aeppic form is a document describing the possible content (a schema) for documents based on that form. It is mostly derived from the form's definition which is written in pure text in formdown
.
formdown
is a dialect of markdown which is defined for the purpose of declaring forms as easily as possible.
Why a text based format ?
We chose to use text for forms as it makes versioning and understanding very easy. Visual form editors can be written on top of it though if needed. It also better aligns with the desire to keep data very open and not hidden in very technical formats.
Fields
A form is normal markdown that also supports fields. A field is marked in a similar fashion to markdown links but with square brackets.
md
[Last Name][lastName]
This defines a field called lastName
using the label Last Name
at that position. The field name must be a valid javascript identifier.
To define the type of a field, a separate line with a syntax similar to markdown hrefs is used. This line should reference the field and provide additional information, like the field's type.
The default type of a field is text
and for simple fields this is all that is needed. The two fields definitions are actually called placeholders and the type and other data of the field is implied.
md
[Number of calls][callCount]
[callCount]: number
Here we define callCount
to be of type number
.
It does not matter where the field type info appears but it makes sense to keep the field type definition close to the placeholder it is used in. Usually at the end of the corresponding section or paragraph if the section is long. This is similar to how urls are referenced in markdown.
Note
Number of calls
is the label,callCount
the field name and this can be referenced multiple times in a form.
Multiple fields can also be put next to each other in one line.
md
[First Name][firstName] [Last Name][lastName]
Sections
As with any markdown, it supports headers which are used to seperate a form into sections. The top section is called the root section. By default the root section contains all other sections, but if the top content is already a header and everything else is beneath that header level it is the root section.
md
My content
[Name][name]
# My first subsection
Some more content
# My second subsection
md
# My top header
## My subsection 1
### My subsection 3
When a document uses multiple top-level sections they are usually rendered as tabs by the form layout. Any content before the first section is part of the header.
Types
Fields can have different types. Some examples:
md
[age]: number
[birthdate]: date
[startedAt]: datetime
[address]: address
[preferredHand]: select
- unknown
- left
- right
- both
[address]: switch
...
switch
The switch
type represents a boolean value. Simply true
or false
. It is often used to keep track of conditional values.
select
The select type represents a simple drop down box. It has a few different options depending on what is needed.
md
[preferredHand]:
-
- left
- right
- both
The field shows and stores either an empty string (default because its the first value) or the strings 'left', 'right', or 'both'.
It is possible to store and display different values:
md
[preferredHand]:
-
- left: Left
- right: Right
- both: Unknown
Where the value stored is e.g left
, but the UI would show it as Left
.
The initial value can also have a displayed text even if the value it an empty string in this example.
md
[preferredHand]:
- : Unknown
- left: Left
- right: Right
- both: Unknown
Help
A field can be associated with some text which is usually displayed via a tooltip. This is done by appending a double quoted text behind the type.
md
[age]: number "The contact's age at the time of the call"
It is also possible to provide extended help and other sections in markdown format.
md
[age]: number
help:
---
This is some *help* text in markdown format
[flightReadiness]: select
- DEFAULT: Nothing selected
- GREEN: Green Contact level
- AMBER: Free to fly anything
help:
---
This is some *help* text in markdown format
Tags
A field can also be tagged with various metadata.
md
[field with validators][fieldWithValidators]
[fieldWithValidators]: <required min-length=10>
[Sensitive Value][sensitiveField]
[sensitiveField]: <optional sensitive expiry=5-years classification=dsgvo-5>
If a field is defined twice the last one overrides the initial value
INFO
Every tag=value
segment inside the <...>
is available later under the placeholder and field meta information. When a value is specified it is applied as a string. When no value is specified (e.g <optional>
) it is treated as a flag and its value is true. There is no automatic conversion from tag=false
to boolean or anything. If the tag is unwanted remove it or handle it as a string.
There are some well known tags that have some built-in meaning.
Tag Name | Description |
---|---|
optional | Marks a field as optional. This means this field can be set to undefined |
required | Marks a field as required which is used by validation policies |
min-length | Used by the validation policies |
max-length | Used by validation policies |
Validation
From a data model perspective, it usually makes sense to allow all fields to be empty when working with forms, as this increases the flexibility of workflows a lot. When important information is required before further processing either manually or by business rules any validation policies can be verified before allowing further processing.
Using the required
tag (See above)
Styling Directives
Fields and sections can have certain usability and styling rules associated with it. This is done with curly braces in front of the label.
Showing and hiding fields and sections
In some scenarios, it might be necessary to show or hide fields or entire sections within a form. There are two primary mechanisms for achieving this:
- Show or hide values in the UI
- Set data fields to
undefined
{show if ...} | {hidden if ...}
The styling directive placed before a field's placeholder or field definition can be configured to show or hide a field as needed.
md
# My form
[Shipping Address][shipping]
[Different Billing Address][differentBillingAddress]
[differentBillingAddress]: Switch
{show if differentBillingAddress}[Billing Address][billing]
When differentBillingAddress
is false, the user-interface control for the billing address will be hidden. It's important to note that hiding the control will not reset any of the values inside it. In most use cases, this approach is sufficient, but any code relying on this information will also need to consider the status of the switch field.
Using undefined
As an alternative, a field or an entire section of fields can be marked as completely unavailable by setting their values to undefined
. Fields with undefined
values will not be rendered, and sections containing only undefined
fields (including all subsections) will not be displayed either.
Note: Undefined fields are only allowed when the form of that document (the exact version) explicitly permits them through the
allowUndefinedFields
setting.
When a form includes undefined fields, it's important to ensure that all formulas can handle this situation. In particular, expressions like {show if myRefField.id}
could fail if not properly handled. To address this, use the ?
syntax: {show if myRefField?.id}
. This approach allows the form designer to decide how to manage these cases.
You can also additionally tag those fields as optional. The default form rendering layout will use that information to allow users to show and hide such sections.
Rendering
Forms are rendered by the ae-form
directive. The rendering process is explained on a conceptual level here.