@rainbow-d9/n3
v1.2.26
Published
d9 markdown
Downloads
286
Readme
d9-n3
It is the No.3 project of group d9
.
This project is Markdown engine, parse to JSON configuration, which used to do rendering.
Idea
The purpose of d9-n3
is to provide a Markdown-based approach that allows non-programmers to easily participate in the process of page
design and creation to a great extent, without the assistance of programmers. Additionally, due to the easily storable and comparable
structure of Markdown, this approach also brings convenience in terms of page management.
Add Into Your Project
yarn add @rainbow-d9/n3
Parse
Parsing Markdown involves three main steps: pre-parsing of Markdown content, syntax parsing, and creation and parsing of widget configurations.
Preparse
The pre-parsing of Markdown content uses mdast
for parsing. It mainly processes the text content into a structured format and
builds the structure tree of the given document based on the hierarchy of headings. It is worth noting that according to the parsing result
of mdast
, all headings (if the syntax follows the normal writing conventions, as Markdown has loose syntax requirements) are placed under
the root node and are not organized into a tree structure. Therefore, the pre-parsing process reorganizes the headings based on their
hierarchy, with N-level headings appearing as child nodes under the nearest preceding N-1 level heading.
Semantic Parse
The semantic parsing will parse headings and list items into components, following the following parsing rules:
- Headings, in the format of
WidgetType[::Headline][::$Id]
.- If no text definition is provided, it is considered a reserved heading,
- If the text ends with
::IGNORE
or::EXPORT
, it is considered a reserved heading., - If no widget type is detected, it is considered a reserved heading.
- List Items, content can be defined as a component or property definition.
- If the first child element of the list item in Markdown syntax is not text, it is considered a reserved item.
- If the first child element is not on the same line as the list item, it is considered a reserved item.
- If there is no text definition on the same line, it is considered a reserved item.
- If it starts with
REF.
,Ref.
, orref.
, it is considered a reference to an external component. - If the text format is
WidgetType::[Label[::PropertyPath]]
, it is considered a component. - If it is in the format
x: y
, it is considered a property definition. - Others are considered a property group definition, specifically used for defining boolean properties.
Reserved heading and list item currently not supported in the runtime.
Reference list item currently not supported in the runtime.
Creation and Parsing of Widget Configurations
After the pre-parsing and syntax parsing stages, we have obtained a complete syntax tree. The remaining task is to continue parsing and
creating configuration data that can be used for rendering with the d9
core.
Before explaining the final step, an important concept must be clarified: the parsing of Markdown syntax has no direct physical
relationship with the actual widget library selection. However, logically, all widget types parsed in the previous
process must be supported in the final rendering process. In particular, the parsing of specific widget properties, due to the uniqueness
of each individual widget (each widget may have its own unique properties and corresponding values), requires support from a
corresponding widget parsing implementation if we are rendering based on a specific widget library. However, d9-n3
already has
standard support for standard properties, and if there are no specific properties that need support (which is highly unlikely in the real
world), there is essentially no need for additional widget-level plugin support.
All discussions below are based on the parsing support for the d9-n2
widget library that comes with d9-n3
. If you are using a
different widget library or a mixed scenario, you can also customize and register your own widget parsing by understanding the
following instructions and referring to the relevant source code of d9-n3
.
Referencing the implementation of SemanticHelper#classifyParsedHeadings
, Markdown allows multiple top-level nodes. Currently, only nodes
with a widget type of Page
are considered renderable widgets. In the absence of any Page
node definitions, only nodes defined as
Export
will be considered renderable widgets. In the final parsing process, only the first node considered renderable will be selected
for further parsing and ultimately rendered.
Let's take a look at a very simple example,
# Page
- Input::Name::name
# Page
[//]: # (something else)
In the above definition, only the first Page
node will be considered for rendering, and the second one will be ignored. Therefore, in
actual practice, we recommend defining one page per Markdown and not merging them together.
Regarding the inclusion of multiple widgets in a Markdown file, reserved headings, reserved list items, and related support, we will plan for it in future versions. Currently, it can be understood as just a placeholder to support flexibility in definitions.
After the initial demonstration, we have learned how to define a page using a Markdown file. Next, we will see a more complex example as follows:
# Page::Policy Info
## Section::Policy Info
- Input::Company Code::companyCode
- length: 2;
- disabled
- Input::Prefix::symbol
- length: 3;
- disabled
- Input::Policy No.::policyNo
- disabled
- length: 6;
- Input::Suffix::renewalRewriteVer
- length: 1..2;
- disabled
- Dropdown::Country::countryCode
- options:
- 183: NZ
- Input::Insured Name::insuredName
- length: 0..128;
- Input::Insured Alias Name::insuredAliasName
- length: 0..40;
- Input::Holder Contact Phone::holderContactPhone
- length: 0..15;
- Input::Endorsement No.::endoNo
- length: 3;
- disabled
- Input::Endorsement Cancel Ver::endoCancelVer
- length: 1..2;
- disabled
- Input::Ccvb No.::ccvb
- disabled
- Dropdown::Transaction Type::transactionType
- options:
- AP: New Business
- CP: Cancel Policy Pro-rata
- MP: Endorsement
- RENEW: Renew
- REIN: Reinstatement
- Date::Trans Date::transDate
- Date::Application Date::applicationDate
- Dropdown::Status::status
- options:
- A: Active
- C: Cancelled
- Input::Previous Status::previousStatus
- length: 1;
- Date::Policy Effective Date::effectiveDate
- Date::Policy Expiry Date::expiryDate
- Input::Terms::terms
- length: 1..3;
- integer: Must be an integer.
- Date::Origin Issue Date::originIssueDate
- Date::Origin Effective Date::originEffectiveDate
- Date::Endorsement Effective Date::endoEffectiveDate
- Date::Cancel Date::cancelDate
- Date::Cancel Process Date::cancelProcessDate
- Input::Cancel Reason Code::cancelReasonCode
- length: 2;
- Date::Reinstate Process Date::reinstateProcessDate
- Dropdown::Reinstate Type::reinstateType
- options:
- P: P
- Dropdown::Branch::branchCode
- options:
- 01: 01
- 02: 02
- Input::Underwriter Code::underwriterCode
- length: 0..10;
- Input::Underwriter Origin::underwriterOrigin
- length: 0..10;
- Dropdown::Broker Company Code::agentCode
- options: @ext.codes.mdBrokerCompanyOptions
- sort: asc
- Input::Broker Name Code::brokerCode
- length: 0..6;
- Number::Commission Percentage::commissionPercentage
- notNegative: Must be not negative.
- numeric: Must be numeric.
- Dropdown::Account Type Code::accountTypeCode
- options:
- A: A
- C: C
- B: B
- Y: Y
- Input::Source Code::sourceCode
- length: 1..3;
- Input::Production Source Code::productionSourceCode
- length: 1..3;
- Dropdown::Coinsurance Indicator::coinsuranceIndicator
- options:
- N: N (Non-Coinsurance)
- L: L (Co-AIG as Leader)
- Y: Y (Co-AIG as Follower)
- Dropdown::Renewal Flag::renewalFlag
- options:
- 0: N
- 1: Y
- Dropdown::Renewal Code::renewalCode
- options:
- 00: 00
- 01: 01
- 02: 02
- 03: 03
- 04: 04
- 05: 05
- 06: 06
- 07: 07
- 08: 08
- 09: 09
- 10: 10
- Dropdown::Renewal Indicator::renewalIndicator
- options:
- N: N
- Y: Y
- Input::Assume Business Indicator::assumeBusinessIndicator
- length: 1;
- Dropdown::Inward RI Company Code::inwardRICode
- options: @ext.codes.mdInwardRiCompanyOptions
- sort: asc
- Dropdown::Assumed Type::agentSubproducerCode
- options:
- NRI: NRI
- IRI: IRI
- Dropdown::Billing Code::billingCode
- Options:
- AC:AC
- DB:DB
- AR:AR
- Input::Pay Plan Code::payPlanCode
- length:2
- Dropdown::Currency Code::currencyCode
- options:
- 108: 108
- Input::Quote Currency Code::quoteCurrencyCode
- length: 3;
- Dropdown::Deductible Costs Flag::deductibleCostsFlag
- options:
- E: E
- I: I
- N: N
- Dropdown::Rated Type::ratedType
- options:
- M: M
- A: A
- H: H
- P: P
- Number::Short Rate Percentage::shortRatePercentage
- numeric: Must be numeric.
- notNegative: Must be not negative.
- Date::Reinstatement Date::reinstatementDate
- Input::Renewal Certificate Number::renewalCertificateNumber
- length: 8;
- Dropdown::Audit Frequency::auditFrequency
- options:
- A: A
- S: S
- M: M
- Dropdown::Personal Package Policy Status::personalPackagePolicyStatus
- options:
- A: A
- H: H
- B: B
- Input::Stateside Collection Indicator::statesideCollectionIndicator
- length: 0..1;
- Dropdown::MM Maker Type::mmMakerType
- options:
- MJ: MJ
- MM: MM
- M: M
- Checkbox::eDit Flag::eDitFlag
- values: Y, N
- Input::Request Revision No.::requestRevisionNo
- Input::Lae Method Flag::laeMethodFlag
- length: 1;
- Dropdown::Prefix Segment Code::prefixSegmentCode
- options:
- C: C
- S: S
- T: T
- A: A
- O: O
- N: N
- 0: 0
- Dropdown::Waats Gds Reference No.::waatsGdsReferenceNo
- options:
- 0: 0
- NZD: NZD
- ANZ: ANZ
- FRA: FRA
- Input::Document Note::documentNote
- disabled
- Dropdown::Print Renewal Notice::printRenewalNotice
- options:
- Y: Y
- Date::Error Date::errorDate
- Input::Form Number::formNumber
- length: 0..5;
- Dropdown::Region Field::regionField
- options:
- N: N
- Input::Renewal Extract::renewalExtract
- length: 0..1;
- Checkbox::Has End::hasEnd
- values: Y, N
- Input::Filler Field::fillerField
- place: 12
- length: 0..65;
- Input::Manual Modify::manualModify
- disabled
The above is a fairly comprehensive example used to render a page for inputting basic insurance information. We don't need to focus on the specific content here; we are interested in understanding how to define it quickly. Let’s select a representative section from it for a detailed observation.
# Page::Policy Info
## Section::Policy Info
- Input::Company Code::companyCode
- length: 2;
- disabled
- Dropdown::Country::countryCode
- options:
- 183: NZ
- Checkbox::Has End::hasEnd
- values: Y, N
- Input::Filler Field::fillerField
- place: 12
- length: 0..65;
# Page::Policy Info
: This represents a page,## Section::Policy Info
: This is a Section, and it is titledPolicy Info
,- Input::Company Code::companyCode
: This is an input, and it is titledCompany Code
, bind two-way with the model propertycompanyCode
,- length: 2;
: The length ofCompany Code
must be 2 characters. Custom validation exception messages can be written after the semicolon, but it is not specified in this case, so the standard message will be used,- disabled
:Company Code
cannot be edited or modified,- Dropdown::Country::countryCode
: This is a dropdown, and it is titledCountry
, bind two-way with the model propertycountryCode
,- options:
,- 183: NZ
: This dropdown only has one option, with a value of183
and a label ofNZ
,- Checkbox::Has End::hasEnd
: This is a checkbox, and it is titledHas End
, bind two-way with the model propertyhasEnd
,- values: Y, N
: The value of this checkbox isY
andN
,- Input::Filler Field::fillerField
: This is an input, and it is titledFiller Field
, bind two-way with the model propertyfillerField
,- place: 12
: Occupying 12 columns, since theSection
uses a 12-column layout, this input field actually spans the entire row,- length: 0..65;
: Similar to the length limit mentioned above, here it is defined that 0 to 65 characters are all valid.
Once the above Markdown is defined, simply invoke the parseDoc
function to obtain the parsed configuration, which can then be used for
rendering, as shown below:
// Assuming that content is a markdown text definition that satisfies the syntax requirements,
const {node: def} = parseDoc(content);
return <StandaloneRoot $root={model} {...def} />;
Built-in d9-n2
Widgets Parser
All the built-in d9-n2
widgets parsing support can be found in the /src/lib/n2
directory. Before using the d9-n2
widget parsing,
need to call the registerN2Widgets
method to register all the parsers. All parsers need to
implement SpecificWidgetTranslator
. If it is an array widget, please implement SpecificArrayWidgetTranslator
. d9-n3
already
provides a variety of basic parsers for customizing new parsers, and you can observe the specific implementation through the source code.
After writing your custom parsers, you also need to provide a function similar to registerN2Widgets
to register your parser
into the parser repository.
Syntax of Markdown
When defining a page, the most important aspect is attribute definition. This section will focus on the attribute definition part and provide comprehensive documentation to facilitate quick learning for users.
Property or attribute have the same meaning in this chapter and are no longer distinguished.
Heading
Syntax: WidgetType[::Headline[::PropertyPath[::Id]]]
.
Connect with ::
,
- If there is no
::
, only the widget type is present, - If there are two parts, it includes widget type and headline,
- If there are three parts, it includes widget type, headline and property path,
- If there are more than three parts,
- The last section represents the id,
- The second to last section represents property path,
- The first section represents the widget type,
- Rest sections are reconnected with
::
to represent the headline.
Some examples:
# Page
## Section::Basic Info
### Table::Order Items::orderItems
- property: items
It is important to note that the last section of the heading is not a property path, so an additional property path definition is needed through the list item approach.
If heading ends with
::EXPORT
, means it is exported from this markdown configuration, the first one will be rendered if there is noPage
present.
If heading ends with::IGNORE
, means it is ignored in rendering, which can be used to annotate exclusions that are not meant to be removed from the entire configuration.
Except for the root node, the content of the
headline
will be used as thelabel
attribute.
List Item
If list item ends with
::IGNORE
, means it is ignored in rendering, which can be used to annotate exclusions that are not meant to be removed from the entire configuration.
Widget
Syntax: WidgetType::[Label[::PropertyPath]]
.
Connect with ::
,
- If there is no
::
, it will not be recognized as a widget, - If there are two parts, it includes widget type and label (could be empty or blank),
- If there are more than three parts, the last section represents the property path, the first section represents the widget type. All the
middle sections are reconnected with
::
to represent the headline.
Some examples:
- Input::Name::name
- Dropdown::
- property: gender
- Dropdown::
will be parsed to two parts, which presents widget type and an empty label.
Reference Widget
Syntax: REF.Id
or Ref.Id
or ref.Id
.
Reserved, currently not supported in runtime.
Attribute
Syntax: X:y
.
Single attribute definition. Additionally, this list item node can contain child lists, depending on the implementation of the parser.
Some examples:
- property: name
- options: @ext.codes.hasEndOptions
- options:
- A: A
- C: C
In the examples above, all property definitions are written at the top level. However, in actual definitions, properties are always associated with a component and therefore cannot be written at the top level.
Attributes
Syntax: a[, b[, !c]]
.
To define multiple properties with boolean values, connected by commas. To represent a false value, simply prefix it with an exclamation mark.
Some examples:
- disabled
- disabled, !visible
In the examples above, all property definitions are written at the top level. However, in actual definitions, properties are always associated with a component and therefore cannot be written at the top level.
Position
Syntax:
place
,position
,pos
and$pos
can all be used as attribute names, and they have the same meaning,place: columns
,place: row, column
,place: row, column, columns
,place: row, column, columns, rows
,place: [c|$c|col|$col|column|$column: column][, r|$r|row|$row: row][, cols|$cols|columns|$columns: columns][, rows|$rows: rows]
.
Some examples:
- place: 12
- position: 2, 4
- pos: 2, 4, 6
- $pos: 2, 4, 6, 3
- place: c: 4, r: 2, cols: 6, rows: 3
- place: $c: 4, $r: 2, $cols: 6, $rows: 3
- $mpos: 2, 4, 6, 3
- mpos: 2, 4, 6, 3
$mpos
,mpos
is position for mobile only.
Render On Specific Devices
Specify specific widgets to render only on particular devices using $renderOn
or renderOn
. The valid values
are desktop
, mobile
, tablet
, and touchable
. You can also specify multiple devices by connecting values with ,
or ;
. It's
important to note that if no rendering device filter is applied, the component won't render at all, and thus, won't have any event
listeners. If no device is specified, it will be assumed to render on all devices.
data-
Attribute
Attributes starting with data-
are standard HTML DOM attributes. For these attributes, the following enhancements apply:
- First, standard attributes like boolean values, numeric values, etc.
- If it's a string type, check if it starts with
$pp.
. If it does, consider it needs to read a value from the model. If not, consider it as aDataAttributeCalculator
function. - Otherwise, use the original value.
It's important to note that data-
attributes can be reused, for example, when a component is wrapped by a form cell. In such usage
scenarios, if a data-
attribute is associated with its own property value changes, it can only be perceived by the component itself,
rather than directly by the form cell. Therefore, it's necessary to combine it with the repaint
definition to enable the entire form cell
to perceive it.
Tips
The built-in tip can be easily activated by adding some attributes to the component. The following attributes are necessary:
data-tip-body
: Essential for activating the tip. If it has no value, the tip will not be activated.data-tip-title
: Title of the tip.data-tip-min-width
: Minimum width.data-tip-max-width
: Maximum width.data-tip-max-height
: Maximum height.data-tip-delay
: Display duration in seconds.data-tip-tag
: Custom tip DOM attribute name, used for customizing tip styles.
Most complex components do not support the tip feature, as there isn't actually much necessity for it. If additional support is needed, you can use a Box to wrap around the component.
Components with pop-up layers, such as Dropdowns and Calendars, although supported, are prone to conflict with Popups, so it's not recommended to use them. If you must use them, please use them with a delay to avoid overlapping issues.
For decorator components, the tip feature utilizes the
data-di-*
attributes and also supportsdata-*
, but it's not recommended to use them simultaneously.
Attribute Guard
Any attributes that are not captured by a specific parser will be eventually parsed by the attribute guard. The attribute guard follows the following principles for parsing:
- If the attribute value is empty, it is considered as an empty string.
- If it is any of
True
,true
,T
,t
,Yes
,yes
,Y
,y
, it is considered as true. - If it is any of
False
,false
,F
,f
,No
,no
,N
,n
, it is considered as false. - Attempt to convert it to a numeric type, if successful, it is converted to a numeric value; if failed, the string value is retained.
Therefore, if the value of a component attribute conforms to the above rules, there is no need to provide additional parsers.
Some examples:
- property: name
- disabled: yes
External Definition
Any attribute value starting with @ext.
will be translated as requiring an external definition.
Some examples:
- options: @ext.codes.hasEndOptions
In the examples above, using
codes.hasEndOptions
to find external definitions, which passed toStandardRoot
.
Unless otherwise specified, in markdown, the
@ext.
syntax is only allowed when declaring properties in the first level under a component. Many properties of components are complex and require multi-level description. In such cases, the@ext.
syntax is generally not supported.
Form Cell
$fc
is a boolean attribute. When this attribute is defined (typically with a value of true), the current widget will have
the .FC
suffix added, indicating that it is included within the component represented by FC
. Note that if a widget
already has a label defined, it will by default have the .FC
suffix added. Unless the widget parser declares that it should not be
included, please refer to the SpecificWidgetTranslator#shouldWrapByFormCell
implementation.
Form cell introduces the concept of label
, which is generally a static string and can be defined using - label: SomeText
. In complex
cases, component combinations can be defined using the following approach, as an example, we will take the input box:
label
in headline,- Input::A Label::name
label
in attribute,- Input::::name - label: A Label
- Use a
Caption
aslabel
, in this case, label is same as input text,- Input::::name - label: - valueOnLabel - property: name
- Use any widget as
label
,- Input::::name - label: Input - property: name
Please be aware that label
can be an attribute of widget itself, in this case,
use SpecificWidgetTranslator#shouldTranslateLabelAttribute
to avoid default parsing behavior.
Built-in Validation Properties
required
: boolean,numeric
: boolean,integer
: boolean,positive
: boolean,notNegative
: boolean,length
: syntax as below,length: number
: presents fix length,length: number..
: presents minimum length,length: ..number
: presents maximum length,length: number..number
: presents both minimum and maximum length,- all above syntax, connected by
,
, - no negative value accepted.
numberRange
: : syntax as below,numberRange: [min..max]
: presents a range,- allow given minimum value by
[
, or ignore it. Or exclude given minimum value by(
, - allow given maximum value by
]
, or ignore it. Or exclude given maximum value by)
, - minimum/maximum value can be ignored, but at least one should be present,
- all above syntax, connected by
,
,
regex
orregexp
: syntax as below,regex: ^\d+$
: presents a regex pattern,- multiple patterns connected by
,
.
Some examples:
- required, numeric, positive
- integer, notNegative
- length: 5
- length: 1..5
- length: ..5
- length: 3..
- length: 8, 11
- length: 5..8, 11..20
All built-in validation properties can use tailing ; message
to identify the customization message. For boolean attribute, string value
should be treated as customization message.
Some examples:
- required: Name is required.
- length: 5; Name should be 5 characters.
Built-in validation attribute must be declared in customized
SpecificWidgetTranslator
, otherwise it will not take effect.
Name Mapping
The names of certain attributes in actual configurations may start with a $
, or they may be abbreviated or use shorthand
forms. These names can be confusing to read when configuring in Markdown. Therefore, d9-n3
provides a mechanism for attribute name
mapping, which consists of the following parts:
- Widget scoped: by
WidgetType.name
register, - Global: by
name
register, - Built-in.
Some examples:
export class N2DropdownTranslator extends SpecificWidgetTranslator<N2WidgetType.DROPDOWN> {
// declare sort is mapping to optionSort, for dropdown only
// in markdown, eg. - sort: asc
public getAttributeNamesMapping(): Undefinable<Record<CustomAttributeName, WidgetPropertyName>> {
return {'Dropdown.sort': 'optionSort'};
}
}
// declare sort is mapping to optionSort, for all widgets
AttributeNameUtils.register({'sort': 'optionSort'});
Built-in name mapping as below,
| Name in Markdown | Name in Definition | |------------------|--------------------| | disabled | $disabled | | visible | $visible | | validate | $valid | | watch | $reaction | | property | $pp | | place | $pos | | position | $pos | | pos | $pos |
d9-n2
Widgets Support
Common
| Attribute Name | Type | Need Declare by Widget Parser? | Description |
|-----------------------------------|---------------|--------------------------------|----------------------------------------------------------------------------------------------|
| property, $pp | property path | No | - property: name
- property: customer.name
|
| disabled, $disabled | boolean | No | - disabled
|
| visible, $visible | boolean | No | - visible: false
|
| validate, $valid | various | No | |
| validateScopes, $validationScopes | text | No | - validateScopes: s1, s2
Define the applicable validation scopes. |
| watch, $reaction | various | No | |
| place, position, pos, $pos | various | No | Refer to Position |
| $fc | boolean | No | - $fc
Force current widget wrapped by a form cell. |
| label | text | No | - Section::Customer
- label: Customer
Works when widget is wrapped by form cell. |
| holdPositionWhenInvisible | boolean | No | Hold position even widget is invisible, for form cell. |
| required | boolean | Yes | - required
- required: This field is mandantory.
|
| numeric | boolean | Yes | - numeric
- numeric: This field should be a number.
|
| integer | boolean | Yes | - integer
- integer: This field should be an integer.
|
| positive | boolean | Yes | - positive
- positive: This field should be a positive number.
|
| notNegative | boolean | Yes | - notNegative
- notNegative: This field should be a non-negative number.
|
| length | number range | Yes | - length: 5
- length: 5..10
, etc. |
| regex,regexp | regexp | Yes | - regex: ^\d{5}$
, etc. |
Disablement and Visibility
For components that support responsive availability and visibility, besides directly using boolean values, $disabled
and $visible
provide more complex definition approaches as follows:
- Checkbox::Allowed to watch PG-13 rated films.::allowPG13
- disabled:
- on: name, age
- handle: `(model.name ?? '').trim().length === 0 || parseInt(model.age ?? 0) <= 12`
The above syntax indicates that if the name
is not filled in or the age
is less than or equal to 12, the current component will be
disabled.
handle
can be defined using a JavaScript block to enable syntax highlighting. It also supports the@ext.
syntax.
It is important to note that for
disabled
andvisible
, thehandle
definition will also be called as a default value calculation. Therefore, please note that within thehandle
function body, only theroot
,model
, andvalue
arguments are applicable.
Validation
In addition to the built-in validation rules, you can also customize validation rules in the same way as Disablement
and Visibility
.
However, Validation
can choose not to listen to any other property changes. If defined, it will be applied to all validation rules.
Validation
can also be defined with only the listening properties without defining any rules.
Here is a simple example:
- Input::Property D::propD
- required
- validate:
- on: propA
- handle:
```javascript
if (VUtils.isBlank(model.propA)) {
return value === 'blank' ? {valid: true}: {valid:false, failReason: 'A is blank, D should be "blank".'};
} else if (VUtils.isNumber(model.propA).test) {
return value === 'number' ? {valid: true}: {valid:false, failReason: 'A is number, D should be "number".'};
} else {
return value === 'string' ? {valid: true}: {valid:false, failReason: 'A is string, D should be "string"'};
}
```
The above definition means that property D
is required and its value must have a certain mapping relationship with the type of
property A
.
Reaction
Similar to the Disablement
, Visibility
, and Validation
mentioned above, the Reaction
can also be customized. All the syntax rules
for Reaction
are consistent with these three basic attributes. However, Reaction
provides multiple writing styles, including the
possibility of having special Reaction
keywords for components. Here are several common Reaction
keywords that all components have:
repaint
: Refreshes itself when changes are detected.clearMe
: Clears its value when changes are detected and then refreshes itself.watch
: Performs custom operations when changes are detected.
It is important to note that watch
must have a defined handler, but it is not required to have a return value. By default, it
uses repaint
as the standard behavior. As for repaint
and clearMe
, since they already have standard behaviors, in most cases, it is
only necessary to define the listeners without the need to define a handle
.
Here is a simple example:
- Input::Property D::propD
- required
- repaint:
- on: propA
- Input::Property E::propE
- required
- clearMe:
- on: propA
- Input::Property F::propF
- required
- watch:
- on: propA
- handle:
```javascript
model.propF = model.propA;
return 'repaint';
```
Additionally, if using watch
, the return can also specify changes in attribute values, so that the component can actively initiate
attribute value change events. This is typically used when it is necessary to notify other components after the response itself has made
certain changes to the data model.
Here is a simple example:
- Input::Property G::propG
- watch:
- on: propG
- handle:
```javascript
const oldValue = model.propH;
model.propH = model.propG;
// path must be absolute
return ['value-changed', {path: '/propH', from: oldValue, to: model.propH}];
```
Internationalization
Use $d9n2.intl.labels
to define the internationalization string package. For example, you can add $d9n2.intl.labels.zh
to define a
Chinese package. After the definition is completed, use the following command to notify the language switch:
``
const {fire} = useGlobalEventBus();
const onZhClicked = () => {
$d9n2.intl.language = 'zh';
fire(GlobalEventTypes.LANGUAGE_CHANGED, 'zh');
};
Please note that the language package
$d9n2.intl.labels['en-US']
already exists and can be overwritten to override the built-in string package.
Page
Strictly adhere to the heading parsing rules without any additional attribute definitions.
Section
- Default Wrapped by Form Cell:
false
, - Default Grid Column Span:
12
.
| Attribute Name | Type | Description |
|----------------|---------|-----------------------------------------------------------|
| label, title | text | - Section::Customer
- title: Customer
|
| collapsible | boolean | Default false
. |
| collapsed | boolean | Default false
, only works when collapsible
is true
. |
label
andtitle
attribute follows thelabel
default parsing behavior.
Box
A container, default use flex layout.
Tabs
- Default Wrapped by Form Cell:
false
, - Default Grid Column Span:
12
.
| Attribute Name | Type | Description | |----------------|--------------|--------------------------------------------------------------------------| | initActive | number, text | Number represents tab index (starts from 0), text represents tab marker. |
Tab
| Attribute Name | Type | Description | |----------------|----------|----------------------------| | title | text | Tab title. | | marker | text | Tab marker, global unique. | | badge | text | Tab title badge. | | body | function | | | data | function | |
title
andbadge
attribute follows thelabel
default parsing behavior.
The configuration of tab content is the same as Section
, except follows,
- No
title
attribute, which has already been used in tab title, - No
label
attribute.
Ignore all child node definitions when
body
presents.
data
attribute is used to retrieve the data of the tab content, simply set data into tab model and widget will refresh automatically.
Wizard
- Default Wrapped by Form Cell:
false
, - Default Grid Column Span:
12
.
| Attribute Name | Type | Description | |----------------|--------------|----------------------------------------------------------------------------------------------------------------------------------------| | balloon | boolean | Use ballon style or not, default true. | | emphasisActive | boolean | Enable active emphasis animation or not, default true. | | freeWalk | boolean | Freely switch between all the steps or not, default false. | | omitWalker | boolean | Omit the default step switching buttons (forward/backward) or not, default false. | | reached | number, text | Number represents step index (starts from 0), text represents step marker. All steps before the specified step can be switched freely. |
Wizard Step (WStep)
| Attribute Name | Type | Description | |----------------|----------|----------------------------| | title | text | Tab title. | | marker | text | Tab marker, global unique. | | body | function | | | data | function | |
title
attribute follows thelabel
default parsing behavior.
The configuration of tab content is the same as Section
, except follows,
- No
title
attribute, which has already been used in tab title, - No
label
attribute.
Ignore all child node definitions when
body
presents.
data
attribute is used to retrieve the data of the step content, simply set data into step model and widget will refresh automatically.
Wizard Shared Content (WShared)
| Attribute Name | Type | Description | |----------------|---------|----------------------------------------------------------------------------| | lead | boolean | Shared content block comes before each step content or not, default false. |
The configuration of tab content is the same as Section
, except follows,
- No
title
attribute, - No
label
attribute.
Caption, Label
- Default Wrapped by Form Cell:
true
, - Default Grid Column Span:
3
.
| Attribute Name | Type | Description |
|----------------|---------|-----------------------------------------------------------------------------------------------------------|
| label, text | text | - Caption::Customer Name
- label: Customer Name
|
| labelOnValue | boolean | - labelOnValue: true
|
| valueToLabel | various | |
| click | text | - click: alert message
- click: alert:message
- click: dialog key
- click: dialog:key
|
Caption
and Label
have slight differences.
- For
Caption
, the model value must be explicitly specified; otherwise, only the givenlabel
will be used,- While defining
valueToLabel
, the parser will automatically setlabelOnValue
totrue
,
- While defining
- For
Label
, defaults to using the model value and ignores thelabel
attribute, - For both, while defining
text
, will ignore thelabel
attribute.
label
andtext
attribute follows thelabel
default parsing behavior.
Syntax of valueToLabel
:
valueToLabel: `value`
: If usingCaption
and specifying to use the model value, no additional decoration applied. If usingLabel
, this can be ignored.valueToLabel: `value ?? ''`
: Use empty text instead when value isnull
,valueToLabel: `$.nf0(value)`
: Format the value, by#0,000
,valueToLabel: `$.nf1(value)`
: Format the value, by#0,000.0
,valueToLabel: `$.nf2(value)`
: Format the value, by#0,000.00
,valueToLabel: `$.nfx(value)`
: Format the value, by#0,000.000
,x
could be3
to99
, identify the fraction digits,valueToLabel: `$.nf(0, false)(value)`
: Format with the given parameters, keep 0 fraction digits, and do not use thousands separator in the example.valueToLabel: `$.df(value)`
: Parse the value by default datetime format and format it to default date format.- Default datetime format:
getDefaultCalendarDatetimeFormat
, - Default date format:
getDefaultCalendarDateFormat
, - Change default formats:
setCalendarDefaults
,
- Default datetime format:
valueToLabel: `$.df(value, {from: 'YYYY/MM/DD', to: 'DD/MM/YYYY''})`
: Parse the value by givenfrom format
and format it toto format
,from
andto
are optional, will use corresponding default format when ignored.
Some examples:
- Label::Name::name
- Label::Middle Name::middleName
- valueToLabel: `value ?? ''`
- Caption::Middle Name::middleName
- valueToLabel: `value ?? ''`
Starting from version v1.2.9
, valueToLabel
not only supports the above single-line syntax but also complete function bodies.
Additionally,
apart from value
and $
(an alias for the formats
parameter), also access other parameters as follows:
options.root
: Root model,options.model
: Current model,options.global
: Global handlers.
Because
Caption
is also enhanced byForm Cell
by default, the behavior of thelabel
attribute onCaption
may seem strange. When not specifying using data from the model value, the samelabel
will be displayed twice. Therefore, if you only want to useCaption
but don't want it to be displayed twice, you should useCaption::
. This way, the system no longer considers it enhanced byForm Cell
and it will not be displayed twice. This usage is common inTable
, and we will discuss it in more detail later.
Syntax of click
click
requires external support in order to respond to the defined event. For example:
import {BaseModel, PropValue} from '@rainbow-d9/n1';
import {PageDef} from '@rainbow-d9/n2';
const markdown = `# Page
- Label::Customer::name
- valueToLabel: value ?? ''
- click: dialog:customerDetails
`;
const DialogDefs: Record<string, PageDef> = {
// ...
};
const Dialogs = () => {
const {on, off} = useGlobalEventBus();
const {show: showDialog} = useDialog();
useEffect(() => {
const openDialog = async <R extends BaseModel, M extends PropValue>(
def: PageDef, models?: { root: R; model: M; }) => {
showDialog(<>
{title}
<DialogBody>
<StandaloneRoot {...someDialogDef} $root={model as BaseModel}/>
</DialogBody>
{footer}
</>);
};
const onCustomEvent = <R extends BaseModel, M extends PropValue>(
key: string, models?: { root: R; model: M; }) => {
if (key.startsWith('dialog ') || key.startsWith('dialog:')) {
const dialogKey = key.slice('dialog '.length).trim();
if (VUtils.isNotEmpty(dialogKey)) {
const def = DialogDefs[dialogKey];
if (def !== null) {
(async () => await openDialog(def, models))();
} else {
console.log(`Custom event[key=${key}] is ignored since no definition found by given dialog key[${dialogKey}].`);
}
} else {
console.log(`Custom event[key=${key}] is ignored since no dialog key detected.`);
}
} else {
console.log(`Custom event[key=${key}] is ignored.`);
}
};
on(GlobalEventTypes.CUSTOM_EVENT, onCustomEvent);
return () => {
off(GlobalEventTypes.CUSTOM_EVENT, onCustomEvent);
};
});
}
const Page = () => {
const def = parseDoc(markdown);
// alert handled by <Alert/>, so no additional supporte required
// dialog is using custom event, which needs to supported by <Dialogs />, and bridge to <Dialog />
return <GlobalEventBusProvider>
<Alert/>
<Dialog/>
<Dialogs/>
<StandaloneRoot $root={model} {...def} />
</GlobalEventBusProvider>;
};
Input, Number, Pwd
- Default Wrapped by Form Cell:
true
, - Default Grid Column Span:
3
, - Declared Built-in Validation:
required
,numeric
,integer
,positive
,notNegative
,length
,regex
.
Some examples:
- Input::Name::name
- required
- length: ..10; At most 10 characters for name.
- Number::Age::age
- required
- integer
- positive
The given regular expression will first match against the predefined expressions, and if no match is found, it will directly parse the string as a regular expression. If the given string ends with
/i
, it is case-insensitive.
Predefine regular expressions throughValidatorUtils.registerRegexps
.
Mask
The mask prompts for input fields support integration from imask, can be defined using relevant attributes:
Input
:Number
:- grouping:
true
to enable number format grouping. please note default fraction digits is 2. - format: customized mask, a
NumberInputFormat
object or a function returningNumberInputFormat
.
- grouping:
If the mask definition for
Number
doesn't meet your requirements, please useInput
for custom masking, while declaring- valueToNumber: true
.
Please note that here
Mask
refers to text formatting and placeholder display, not the same concept as masking sensitive information like PII (Personally Identifiable Information). Content masking should be controlled through data handling models, not just a matter of formatting display.
Decoration
Input
and Number
can have decorations, including leadings and tailings. Both leadings and tailings support standard strings or built-in
icons. If you want to use built-in icons, you need to use $icons.
followed by the icon name, for example, $icons.date
.
- DecoInput::Name::name
- leads: $icons.date
- tails: $icons.time
Multiple decorators could be split by
;
.
Decoration also supported forButton
,Caption
,Label
.
DecoNumber
is available for decoratingNumber
,DecoPwd
is available for decoratingPwd
.
placeholder
for decorated input supports i18n, and will be disabled automatically when mask presents. However, the number mask typically doesn't display the mask placeholder, so the placeholder will still take effect.
The
data-di-*
attributes are used for decorators, while others are applied directly to the input box.
Textarea
- Default Wrapped by Form Cell:
true
, - Default Grid Column Span:
3
, - Declared Built-in Validation:
required
,length
.
Some examples:
- Textarea::Description::desc
- required
- length: 10..256
Checkbox, Radio
- Default Wrapped by Form Cell:
true
, - Default Grid Column Span:
3
, - Declared Built-in Validation:
required
.
| Attribute Name | Type | Description |
|----------------|---------|------------------------------------|
| values | text | - values: Y
- values: Y, N
|
| emptyWhenFalse | boolean | - emptyWhenFalse
|
emptyWhenFalse
is forCheckbox
only.
Syntax of values
The default values for a checkbox are true
and false
, where any value other than true
is considered as false
in the display logic.
However, if the actual model value is not of boolean type, such as 1
and 0
, Y
and N
, T
and F
, etc., it needs to be explicitly
specified.
It is important to note that in JavaScript, the value
1
is considered astrue
.
Radio is a unidirectional form of Checkbox, which means that once selected, it cannot be unselected.
Some examples:
- Checkbox::Agree::agreed
- required
- values: Y, N
- Checkbox::Agree::agreed
- required
- values: Y
- Checkbox::Agree::agreed
- required
- values: , N
Typically,
values
appear in pairs, but in reality, having only one value is also allowed. However, we generally do not recommend such loose data definition as it can lead to data confusion to some extent.
The default mapping will translate
True
,true
,T
,t
,Yes
,yes
,Y
,y
totrue
, andFalse
,false
,F
,f
,No
,no
,N
,n
tofalse
. If a string value is required, use thes:
orS:
prefix to mark it; for example,s:Y
will be recognized as the stringY
, not the defaulttrue
. If a numeric value is needed, use then:
orN:
prefix to mark it.
Dropdown, MultiDropdown, DropdownTree (DDT), MultiDropdownTree (MDDT)
- Default Wrapped by Form Cell:
true
, - Default Grid Column Span:
3
, - Declared Built-in Validation:
required
.
| Attribute Name | Type | Description |
|------------------|----------|-----------------------------------------------------------------------------------------------------------------------------------|
| please | string | Placeholder. |
| clearable | boolean | Value can be cleared or not. |
| filterable | boolean | Options can be filter or not. |
| filterChanged | function | Obtain additional matching options when filtering.Available for Dropdown
and MultiDropdown
only. |
| sort, optionSort | text | - sort: asc
- sort: desc
Value is case insensitive. |
| options | various | |
| couldSelect | function | Returns false when given value cannot be chosen, otherwise it can.Available for DropdownTree
and MultiDropdownTree
only. |
Syntax of options
options: value: label[; value: label[; value: label...]]
: Static options can be defined by separating each option with a semicolon and separating the value and label with a colon,- Use sub-lists to define options, where each sublist item represents an option, and use a colon to separate the value and label,
options: @ext.keys
: referencing external definitions is also possible.
Some examples:
- Dropdown::Agree::agreed
- required
- options: Y: Yes; N: No
- Checkbox::Agree::agreed
- required
- options:
- Y: Yes
- N: No
- Checkbox::Agree::agreed
- required
- options: @ext.codes.yesNoOptions
codes.yesNoOptions
depends on external definitions, it must follow signatureDropdownDef['options']
.
For "DropdownTree," currently only external definitions are supported, as tree nodes are too complex to describe in Markdown.
Reaction to Refresh Options
Use the refreshOptions
attribute to respond to changes and refresh the available options. It is important to note that the options are
still refreshed using the definition of options
. Therefore, if the options
definition is static, even if refreshOptions
is defined,
there will be no changes. In addition, since refreshing the options may result in the originally selected value becoming invalid, clearMe
can be used in combination to handle this situation.
Search
All dropdowns provide default option search functionality and offer two styles, which can be set using setDropdownDefaults
.
Please note that the search functionality is intended for basic text display. If the label provided in the option item is of type ReactNode, the search functionality will not work. In such cases, you should also provide a stringify function within the option item to retrieve the corresponding text content.
Checkboxes (Checks), Radios
In fact, Checkboxes and Radios are just alternative representations of MultiDropdown
and Dropdown
, respectively. Therefore, all
the parameters are the same.
When there are too many options (usually limited to 5 or 6), it is not recommended to use a combination of checkboxes and radios, but rather to revert to a
MultiDropdown
orDropdown
form.
Due to the differences in presentation, Checkboxes and Radios have additional rendering parameters:
| Attribute Name | Type | Description |
|----------------|---------|-------------------------------------------------------------------------------------------------------------------------------------------|
| columns | number | Number of columns in option arrangement. |
| compact | boolean | When there are multiple options in a row, whether to display them continuously or in a table column format. |
| single | boolean | Only one choice available, only for checks, default false. Should use a primitive value instead of an array when single
is enabled. |
| boolOnSingle | boolean | Only one choice available, only for checks, default false. And if want to put a false
as value, set as true. |
Reaction to Refresh Options
It is completely consistent with the Dropdown
, please refer to the previous section.
Calendar, DateTime, Date
- Default Wrapped by Form Cell:
true
, - Default Grid Column Span:
3
, - Declared Built-in Validation:
required
.
| Attribute Name | Type | Description |
|-------------------|----------|---------------------------------------------------------------------------------------------------------------------------|
| please | text | - please: Please select...
|
| clearable | boolean | - !clearable
- clearable: false
Allowed to clear the selected value or not. |
| date | boolean | - date: false
, only for Calendar
|
| dateFormat | text | - dateFormat: YYYY/MM/DD
, follows Dayjs
. |
| time | boolean | - time: true
, only for Calendar
|
| timeFormat | text | - timeFormat: HH:mm:ss
, follows Dayjs
|
| storeFormat | text | - storeFormat: YYYY/MM/DD HH:mm:ss
, follows Dayjs
|
| autoConfirm | boolean | - autoConfirm: true
Selected value should be applied to model automatically on blur or not. |
| autoConfirmOnDate | boolean | - autoConfirmOnDate: true
Selected date should be applied to model automatically on date clicked when no time part. |
| useCalendarIcon | boolean | - useCalendarIcon: true
Use calendar icon instead of caret down. |
| fixedTimeAt | json | For Calendar
, works when time is false; and for Date
. |
| initTimeAt | json | For Calendar
, typically it is not need for date only. |
| couldPerform | function | Returns false when given value cannot be performed, otherwise it can. |
Automatically detect the time format to determine whether to display minutes and seconds. When seconds are present in the time format, minutes will always be displayed.
Formats
All formats have default values, see below for more details,
setCalendarDefaults
: to change default settings,getDefaultCalendarDateFormat
: default of date format,getDefaultCalendarTimeFormat
: default of time format,getDefaultCalendarDatetimeFormat
: default of store format.
Introduce more Dayjs
plugins to support additional date formats. For example, to support the Buddhist calendar:
// in your entrypoint, make sure it runs before rendering
import dayjs from 'dayjs';
import BuddhistEra from 'dayjs/plugin/buddhistEra';
dayjs.extend(BuddhistEra);
Auto Confirm
Auto confirm selected value on blur has default value, see below for more details,
setCalendarDefaults
: to change default settings,isCalendarAutoConfirm
: default of autoConfirm.
Stick Icon
The stick icon default uses the dropdown style, which is the caret down icon. You can change it to a calendar icon through global settings, see below for more details,
setCalendarDefaults
: to change default settings,isStickIconUseCalendar
: default of stick icon.
Fixed Time At, Initial Time At
- fixedTimeAt: start
or- fixedTimeAt: 0
: fix at00:00:00.0
,- fixedTimeAt: end
: fix at23:59:59.999
,- fixedTimeAt: 01:00:00
: fix at01:00:00.0
. Any valid time not23:59:59
, millisecond should be set as0
,- fixedTimeAt: 23:59:59
: fix at23:59:59.999
,- fixedTimeAt: 23:59:59.1
: fix at23:59:59.001
,- fixedTimeAt: 23:59:59.12
: fix at23:59:59.012
,- fixedTimeAt: 23:59:59.123
: fix at23:59:59.123
.
Some examples:
- Calendar::Date of Birth::dob
- required, !time
- fixedTimeAt: start
- Date::Date of Birth::dob
- required
- fixedTimeAt: 0
- DateTime::Registration Time::registrationTime
- required, !clearable
Button
- Default Wrapped by Form Cell:
true
, - Default Grid Column Span:
3
.
| Attribute Name | Type | Description |
|----------------|---------|--------------------------------------------------------------------------------------|
| head | text | - head: **
Text before text
. |
| text | text | - text: Click Me
|
| tail | text | - tail: **
Text after text
. |
| ink | text | - ink: primary
One of primary
, danger
, warn
, success
, info
, waive
. |
| fill | text | - fill: plain
One of link
, plain
, fill
. |
| click | various | |
Syntax of click
Alert and Dialog
Regarding Alert and Dialog, please refer to the description for Caption
as it remains consistent.
Validation
click: validate
,click: validate me
,click: validate:me
: automatically detect the validation range and trigger validation. The detection of the validation range is done in the following order:- Whether the range is specified on the button,
- Whether it is within the range of array sub-elements,
- The closest container range among ancestors,
- The entire range starting from the root node.
click: validate block
,click: validate:block
: automatically detect the validation range and trigger validation. The detection of the validation range is done in the following order:- Whether it is within the range of array sub-elements,
- The closest container range among ancestors,
- The entire range starting from the root node.
click: validate all
,click: validate:all
: trigger validation, the entire range starting from the root node,click: validate scope1[, scope2[, scope3...]]
,click: validate:scope1[; scope2[; scope3...]]
,click: validate:scope1[ scope2[ scope3...]]
: trigger validation, limited to the given scopes only. If multiple scopes are triggered simultaneously, use a comma or semicolon or blank space as separators. All widgets that match at least one scope will trigger automatic validation.
Some examples:
- Button::
- ink: danger
- fill: link
- click: validate block
Button Bar
- Default Wrapped by Form Cell:
false
, - Default Grid Column Span:
12
.
| Attribute Name | Type | Description |
|----------------|------|-----------------------------------------------------------|
| alignment | text | - alignment: left
One of left
, center
, right
. |
Some examples:
- ButtonBar::
- alignment: center
Pagination
- Default Wrapped by Form Cell:
false
, - Default Grid Column Span:
12
.
| Attribute Name | Type | Description |
|----------------------|--------------|---------------------------------------------------------|
| freeWalk | boolean | - freeWalk
, render a dropdown for choose page number. |
| maxButtons | number | - maxButtons: 5
, how many page number buttons. |
| sizes, possibleSizes | number array | - sizes: 5;10
, split by ;
. |
Ribs, RibsView
- Default Wrapped by Form Cell:
false
, - Default Grid Column Span:
12
.
| Attribute Name | Type | Description |
|-----------------------|----------|---------------------------------------------------------------------|
| marker | string | Identity |
| elementTitle, caption | various | Ref to form cell label
definition. |
| useSectionStyleIcons | boolean | Change expand and collapse icons to use section style icons or not. |
| showRowIndex | boolean | Show row index or not. |
| initExpanded | function | |
| noElementReminder | text | - noElementReminder: No Data
|
| addable | boolean | - !addable
.Not available for RibsView
. |
| addLabel | text | - addLabel: Create New One
.Not available for RibsView
. |
| couldAddElement | function | Not available for RibsView
. |
| disableOnCannotAdd | boolean | - disableOnCannotAdd
.Not available for RibsView
. |
| elementAdded | function | Not available for RibsView
. |
| createElement | function | Not available for RibsView
. |
| removable | boolean | - !removable
.Not available for RibsView
. |
| removeLabel | text | - removeLabel: Remove This One
.Not available for RibsView
. |
| elementRemoved | function | Not available for RibsView
. |
| couldRemoveElement | function | Not available for RibsView
. |
| getElementKey | function | |
Syntax of elementTitle
and caption
In addition to directly defining an attribute with its value as the title of the element, this attribute also supports the same syntax as
the Caption
.
elementTitle
andcaption
attribute follows thelabel
default parsing behavior.
Some examples:
- Ribs::
- caption: name
- noElementReminder: No Data
- Input::Name::name
- Dropdown::Gender::gender
- options: F: Female; M: Male
Ribs
andRibsView
are array widgets, and the layout of each element can be described using a sublist. It is important to note that the sublist needs to be placed after the attributes list