RichText
Available since v2.0.18
RichText is a component for editing and displaying formatted multi-line text. The user formats the text with a toolbar: headings, bold, italic, underline, strikethrough, lists, quotes, code and text color.
The value is stored as Markdown, the same format that Yandex Wiki uses.
Basics
How does it look?



The formatting is shown in view mode and in edit mode. The toolbar is shown in edit mode only. See Formatting for what the toolbar can do.
Warning
The value is stored with formatting marks, for example **bold**. If a field stores richText, use the richText type on every widget that shows this field. A widget with another type, for example text, shows the marks as they are.
Info
The supported content size is up to 8,000 characters, formatting marks included.
How to add?
Example
Step1 Add String field to corresponding BaseEntity. The value keeps formatting marks, so the column needs more room than for plain text.
@Entity
@Getter
@Setter
@NoArgsConstructor
public class MyEntity extends BaseEntity {
/** Stores markdown, so it needs far more room than a plain one-line text field. */
@Column(length = 10000)
private String customField;
}
Step2 Add field String to corresponding DataResponseDTO.
@Getter
@Setter
@NoArgsConstructor
public class MyExampleDTO extends DataResponseDTO {
@SearchParameter(name = "customField")
private String customField;
public MyExampleDTO(MyEntity entity) {
this.id = entity.getId().toString();
this.customField = entity.getCustomField();
}
}
Step3 Add to .widget.json.
Step3 Add to .widget.json.
Placeholder
Placeholder allows you to provide a concise hint, guiding users on the expected value. This hint is displayed before any user input. It can be calculated based on business logic of application. It is shown for a Readonly field as well.
Visual editor: since release 3.0.2
Up to and including release 3.0.1, the placeholder is shown only in the Markdown markup mode, the visual editor shows an empty field. Since release 3.0.2, the placeholder is shown in both modes.
How does it look?

not applicable

How to add?
Example
Add fields.setPlaceholder to corresponding FieldMetaBuilder.
@Override
public void buildRowDependentMeta(RowDependentFieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription,
Long id, Long parentId) {
fields.setEnabled(MyExampleDTO_.customField);
fields.setPlaceholder(MyExampleDTO_.customField, "Placeholder text");
fields.setPlaceholder(MyExampleDTO_.customFieldRO, "Placeholder text");
}
Works for List.
not applicable
Works for Form.
Color
not available
Readonly/Editable
Readonly/Editable indicates whether the field can be edited or not. It can be calculated based on business logic of application
Editable
Live Sample ·
GitHub
Readonly
Live Sample ·
GitHub
How does it look?

not applicable




How to add?
Example
Step1 Add mapping DTO->entity to corresponding VersionAwareResponseService.
@Override
protected ActionResultDTO<MyExampleDTO> doUpdateEntity(MyEntity entity, MyExampleDTO data,
BusinessComponent bc) {
if (data.isFieldChanged(MyExampleDTO_.customField)) {
entity.setCustomField(data.getCustomField());
}
return new ActionResultDTO<>(entityToDto(bc, entity));
}
@Override
public void buildRowDependentMeta(RowDependentFieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription,
Long id, Long parentId) {
fields.setEnabled(MyExampleDTO_.customField);
}
Works for List.
not applicable
Works for Form.
Option 1 Enabled by default.
@Override
public void buildRowDependentMeta(RowDependentFieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription,
Long id, Long parentId) {
//
}
Not recommended. Property fields.setDisabled() overrides the enabled field if you use after property fields.setEnabled.
Works for List.
Works for Info.
Works for Form.
Filtering
Filtering allows you to search data based on criteria.
For MyExample field filtering is case-insensitive and retrieves records containing the specified value at any position (similar to SQL Like %value% ).
Plain text
The filter compares only letters and digits, so formatting marks do not get in the way: metal type finds "Metal type".
Default not recommended
The filter searches the stored value with formatting marks: metal type does not find "Metal type", because the bold marks stand between the words (**Metal** type).
How does it look?

not applicable
not applicable

not applicable
not applicable
How to add?
Example
Step 1 Add a column with only the letters and digits of the value to corresponding BaseEntity. The database computes it, the application does not write it.
In the sample the table is created by Hibernate, so the column is described in columnDefinition (PostgreSQL). If the table is created by Liquibase, add the column by the SQL of Step 5.
@Entity
@Getter
@Setter
@NoArgsConstructor
public class MyEntity extends BaseEntity {
/** Stores markdown, so it needs far more room than a plain one-line text field. */
@Column(length = 10000)
private String customField;
/**
* Only the letters and digits of customField, the database computes it (PostgreSQL).
* The filter searches this column, so formatting marks do not get in the way.
*/
@Column(insertable = false, updatable = false,
columnDefinition = "text GENERATED ALWAYS AS (regexp_replace(custom_field, '[^[:alnum:]]', '', 'g')) STORED")
private String customFieldPlain;
}
/**
* Keeps only the letters and digits of the value the user typed in the filter,
* the same way the column customFieldPlain keeps them.
*/
@Component
@EqualsAndHashCode(callSuper = false)
public class PlainTextValueProvider extends AbstractClassifyDataProvider implements ClassifyDataProvider {
@Override
protected List<ClassifyDataParameter> getProviderParameterValues(Field dtoField, ClassifyDataParameter dataParameter,
FilterParameter filterParam, SearchParameter searchParam, List<ClassifyDataProvider> providers) {
dataParameter.setValue(filterParam.getStringValue().replaceAll("[^\\p{L}\\p{N}]", ""));
return List.of(dataParameter);
}
}
@Getter
@Setter
@NoArgsConstructor
public class MyExampleDTO extends DataResponseDTO {
/** The filter of this field compares only letters and digits: of the column and of the value the user typed. */
@SearchParameter(name = "customFieldPlain", provider = PlainTextValueProvider.class)
private String customField;
public MyExampleDTO(MyEntity entity) {
this.id = entity.getId().toString();
this.customField = entity.getCustomField();
}
}
@Override
public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
if (configuration.getForceActiveEnabled()) {
fields.setForceActive(MyExampleDTO_.customField);
}
fields.enableFilter(MyExampleDTO_.customField);
}
upper(column) LIKE '%VALUE%', so the index is built on upper(column).
-- the column, if the table is created by Liquibase
ALTER TABLE my_entity ADD COLUMN custom_field_plain text
GENERATED ALWAYS AS (regexp_replace(custom_field, '[^[:alnum:]]', '', 'g')) STORED;
-- the index for LIKE '%value%'
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX my_entity_custom_field_plain_trgm ON my_entity USING gin (upper(custom_field_plain) gin_trgm_ops);
Restriction
The filter compares only letters and digits, in the column and in the typed value: spaces, punctuation and formatting marks are ignored.
So metal type, metaltype and Metal-Type find "Metal type" alike. The name of a text color and the address of a link are letters too, so they are found as well.
Works for List.
not applicable
not applicable
Not recommended. The filter searches the stored value with formatting marks.
Step 1 Add @SearchParameter to corresponding DataResponseDTO. (Advanced customization SearchParameter)
@Getter
@Setter
@NoArgsConstructor
public class MyExampleDTO extends DataResponseDTO {
@SearchParameter(name = "customField")
private String customField;
public MyExampleDTO(MyEntity entity) {
this.id = entity.getId().toString();
this.customField = entity.getCustomField();
}
}
@Override
public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
if (configuration.getForceActiveEnabled()) {
fields.setForceActive(MyExampleDTO_.customField);
}
fields.enableFilter(MyExampleDTO_.customField);
}
Works for List.
not applicable
not applicable
Drilldown
not available
Validation
Validation allows you to check any business rules for user-entered value. There are types of validation:
1) Exception:Displays a message to notify users about technical or business errors.
Business Exception:
Live Sample ·
GitHub
Runtime Exception:
Live Sample ·
GitHub
2) Confirm: Presents a dialog with an optional message, requiring user confirmation or cancellation before proceeding.
3) Field level validation: shows error next to all fields, that validation failed for
Option 1:
Live Sample ·
GitHub
Option 2:
Live Sample ·
GitHub
The value is checked as it is stored, with formatting marks. In the samples the value can contain no more than 50 characters: "A short note." counts 17 characters (A **short** note.), not 13.
How does it look?




not applicable




How to add?
Example
BusinessException describes an error within a business process.
Add BusinessException to corresponding VersionAwareResponseService.
@Override
protected ActionResultDTO<MyExampleDTO> doUpdateEntity(MyEntity entity, MyExampleDTO data,
BusinessComponent bc) {
if (data.isFieldChanged(MyExampleDTO_.customField)) {
entity.setCustomField(data.getCustomField());
if (StringUtils.length(data.getCustomField()) > 50) {
throw new BusinessException().addPopup(MAX_50_CHARACTERS);
}
}
return new ActionResultDTO<>(entityToDto(bc, entity));
}
Works for List.
not applicable
Works for Form.
RuntimeException describes technical error within a business process.
Add RuntimeException to corresponding VersionAwareResponseService.
@Override
protected ActionResultDTO<MyExampleDTO> doUpdateEntity(MyEntity entity, MyExampleDTO data,
BusinessComponent bc) {
if (data.isFieldChanged(MyExampleDTO_.customField)) {
entity.setCustomField(data.getCustomField());
try {
//call custom function
throw new Exception("Error");
} catch (Exception e) {
throw new RuntimeException("An unexpected error has occurred.");
}
}
return new ActionResultDTO<>(entityToDto(bc, entity));
}
Works for List.
not applicable
Works for Form.
Add PreAction.confirm to corresponding VersionAwareResponseService.
@Override
public Actions<MyExampleDTO> getActions() {
return Actions.<MyExampleDTO>builder()
.action(act -> act
.action("save", "save")
.withPreAction(PreAction.confirm(cf -> cf
.text("You want to save the value?")
)))
.build();
}
Works for List.
not applicable
Works for Form.
Use if:
Requires a simple fields check (javax validation)
Add javax.validation to corresponding DataResponseDTO.
@Getter
@Setter
@NoArgsConstructor
public class MyExampleDTO extends DataResponseDTO {
@SearchParameter(name = "customField")
@Size(max = 50, message = MAX_50_CHARACTERS)
private String customField;
public MyExampleDTO(MyEntity entity) {
this.id = entity.getId().toString();
this.customField = entity.getCustomField();
}
}
Works for List.
not applicable
Works for Form.
Create custom service for business logic check.
Use if:
Business logic check required for fields
Step 1 Create custom method for check.
private void validateFields(BusinessComponent bc, MyExampleDTO dto) {
BusinessError.Entity entity = new BusinessError.Entity(bc);
if (StringUtils.length(dto.getCustomField()) > 50) {
entity.addField(MyExampleDTO_.customField.getName(), "The field 'customField' can contain no more than 50 characters.");
}
if (StringUtils.length(dto.getCustomFieldAdditional()) > 50) {
entity.addField(
MyExampleDTO_.customFieldAdditional.getName(),
"The field 'customFieldAdditional' can contain no more than 50 characters."
);
}
if (!entity.getFields().isEmpty()) {
throw new BusinessException().setEntity(entity);
}
}
Step 2 Add custom method for check to corresponding VersionAwareResponseService.
@Override
protected ActionResultDTO<MyExampleDTO> doUpdateEntity(MyEntity entity, MyExampleDTO data,
BusinessComponent bc) {
validateFields(bc, data);
if (data.isFieldChanged(MyExampleDTO_.customFieldAdditional)) {
entity.setCustomFieldAdditional(data.getCustomFieldAdditional());
}
if (data.isFieldChanged(MyExampleDTO_.customField)) {
entity.setCustomField(data.getCustomField());
}
return new ActionResultDTO<>(entityToDto(bc, entity));
}
Sorting
Sorting allows you to sort data in ascending or descending order.
MyExample field is a text field, so lexicographic sorting by the stored value is used for it.
How does it look?

not applicable
not applicable
How to add?
Example
see more Sorting
Step 1 Add fields.enableSort to corresponding FieldMetaBuilder.
@Override
public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
if (configuration.getForceActiveEnabled()) {
fields.setForceActive(MyExampleDTO_.customField);
}
fields.enableFilter(MyExampleDTO_.customField);
fields.enableSort(MyExampleDTO_.customField);
}
not applicable
not applicable
Required
Required allows you to denote, that this field must have a value provided. By default, RichText field is not required
How does it look?

not applicable

How to add?
Example
Add fields.setRequired to corresponding FieldMetaBuilder.
@Override
public void buildRowDependentMeta(RowDependentFieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription,
Long id, Long parentId) {
fields.setEnabled(MyExampleDTO_.customField);
fields.setRequired(MyExampleDTO_.customField);
}
Works for List.
not applicable
Works for Form.
Additional properties
Editing modes
The settings button (gear) on the toolbar switches the editor:
- Visual Editor - the formatted text (the default mode);
- Markdown markup - shows and edits the value as it is stored, with formatting marks.
A read-only or disabled field always shows the formatted text.
How does it look?


How to add?
Example
Works by default for the richText field, nothing to add.
Field height
The height of the field is set in rows. A row is as high as a line of the text field, a heading takes more room.
| Parameter | Mode | Default |
|---|---|---|
minRows |
view mode (List, Info) | 1 |
maxRows |
view mode (List, Info) | 10 |
editMinRows |
edit mode (Form, inline editing in List); if not set, minRows is used |
5 |
editMaxRows |
edit mode (Form, inline editing in List); if not set, maxRows is used |
10 |
- In view mode, a value longer than
maxRowsis cut: the last row fades out and ends with .... Hovering over ... shows the full value in a tooltip. - In edit mode, the field grows with the text from
editMinRowstoeditMaxRows, then a scroll bar appears. The user can drag the bottom right corner of the field within these limits.
Tip
To show the whole value on Info, set a maxRows that the value never reaches.
How does it look?



How to add?
Example
Add "maxRows", "editMinRows", "editMaxRows" to the field in .widget.json.
Add "minRows", "maxRows" to the field in .widget.json.
{
"name": "MyExampleInfo",
"title": "minRows 2, maxRows 4",
"type": "Info",
"bc": "myExampleBc",
"fields": [
{
"label": "Custom Field",
"key": "customField",
"type": "richText",
"minRows": 2,
"maxRows": 4
}
],
"options": {
"layout": {
"rows": [
{
"cols": [
{
"fieldKey": "customField",
"span": 24
}
]
}
]
}
}
}
Add "editMinRows", "editMaxRows" to the field in .widget.json.
{
"name": "MyExampleForm",
"title": "editMinRows 3, editMaxRows 6",
"type": "Form",
"bc": "myExampleBc",
"fields": [
{
"label": "Custom Field",
"key": "customField",
"type": "richText",
"editMinRows": 3,
"editMaxRows": 6
}
],
"options": {
"layout": {
"rows": [
{
"cols": [
{
"fieldKey": "customField",
"span": 24
}
]
}
]
}
}
}
The defaults for all richText fields are frontend constants richTextMinRows, richTextMaxRows, richTextEditMinRows, richTextEditMaxRows:
Formatting
The user formats the text with the toolbar. When the field is narrow, the buttons that do not fit go to the ⋯ menu. Undo, Redo and Clear Format work for all formatting below.
The value is stored as Markdown, the same format that Yandex Wiki uses. Markdown cannot express some combinations of formatting, and Yandex Wiki fails in these cases too.
✅ supported, ⚠️ supported with a caveat, ❌ not supported, — not checked.
Basics
| # | Feature | Toolbar | Status | Yandex Wiki | Stored as |
|---|---|---|---|---|---|
| 1 | Bold | Bold | ✅ | ✅ | **bold** |
| 2 | Italic | Italic | ✅ | ✅ | *italic* |
| 3 | Underline | Underline | ✅ | ✅ | ++under++ |
| 4 | Strikethrough | Strikethrough | ✅ | ✅ | ~~strike~~ |
| 5 | Inline code | Code: Inline code | ✅ | ✅ | `code` |
| 6 | Text color | Text color: Gray, Yellow, Orange, Red, Green, Blue, Violet; Default removes the color | ✅ | ✅ | {red}(text) |
| 7 | Link | no button, type it in the Markdown markup | ✅ | ✅ | [text](url) |
| 8 | Heading H1–H6 | Heading: Heading 1 … Heading 6; Text makes a paragraph | ✅ | ✅ | # Head … ###### Head |
| 9 | Bullet / ordered list, nested list | List: Bullet List, Ordered List, Sink Item, Lift Item | ✅ | ✅ | - item / 1. item |
| 10 | Blockquote | Quote | ✅ | ✅ | > quote |
| 11 | Code block | Code: Code block | ✅ | ✅ | ```\ncode\n``` |
| 12 | Paragraph (Enter) / line break (Shift+Enter) | keyboard | ✅ | ✅ | a\n\nb / a \nb |
| 13 | Text starting with 4 spaces or a tab stays plain text | keyboard | ✅ | — | text stays text |
Combinations & overlaps
| # | Feature | Status | Yandex Wiki | Stored as |
|---|---|---|---|---|
| 14 | Two+ styles combined | ✅ | ✅ | ***x***, {red}(**x**) |
| 15 | Styles inside heading / list / quote | ✅ | ✅ | # **b** head, - {red}(c) item |
| 16 | Three+ styles overlapping in a staircase | ✅ | ✅ | ++abc**def**++**gh~~ij~~**~~kl~~ |
| 17 | Style across a line break (Shift+Enter) | ✅ | ✅ | **a** \n**b** |
| 18 | Bold and italic overlapping each other | ⚠️ | — | an invisible separator is inserted where they meet |
| 19 | Bold or italic touching a parenthesis | ❌ | ❌ | abc*def)*ghi cannot be written in Markdown; the toolbar disables such formatting |
Text color
| # | Feature | Status | Yandex Wiki | Stored as |
|---|---|---|---|---|
| 20 | Color over text with parentheses | ✅ | ✅ | {red}(Hello \(world\)) |
| 21 | Color across line breaks (Shift+Enter) | ✅ | ✅ | {red}(a) \n{red}(b) |
| 22 | Color on inline code | ⚠️ | — | impossible, inline code excludes other styles |
Restriction
- 13 - a line that starts with 4 spaces or a tab is not turned into a code block. Use Code block for code.
- 18 - where bold and italic overlap, the editor adds an invisible character between them, so both are kept.
- 19 - bold, italic, underline, strikethrough and inline code are disabled for a selection that starts or ends at a parenthesis next to a letter, for example
)definabc)def. Text color works. - 22 - inline code cannot have a color.