Dictionary
Dictionary
is a component that allows to select single value from dropdown.
Tips
Use for dictionaries or slowly-growing entities, e.g. no more than 1000 values (all values are loaded in memory). Otherwise, use inlinePickList
Basics
Dictionary can configurable:
- Enum
- Dictionary (since release 2.0.9)
Not recommended.
LOV deprecated (since release 2.0.9)
Dictionary
enables adding, deleting, and modifying its values through the administrative interface, so it should remain independent of business logic.
If the dictionary is tied to business logic, it is recommended to use Enum
to prevent modifications or deletions via the administration panel.
How does it look?
How to add?
Example
Step 1 Download plugin download Intellij Plugin
Step 2 Add dictionary field to an existing form widget
Add field to .widget.json.
- Step1 Create Enum. Best practice: storing enum name in the Database and using a separate field for displayed UI values
@Getter @AllArgsConstructor public enum CustomFieldEnum { BELGOROD("Belgorod region"), BRYANSK("Bryansk region"), VLADIMIR("Vladimir region"), VORONEZH("Voronezh region"), IVANOVO("Ivanovo region"), KALUGA("Kaluga region"), KOSTROMA("Kostroma region"), KURSK("Kursk region"), MOSCOW("Moscow region"), ORYOL("Oryol region"), RYAZAN("Ryazan region"), SMOLENSK("Smolensk region"), TAMBOV("Tambov region"), TVER("Tver region"); @JsonValue private final String value; public static CustomFieldEnum getByValue(@NonNull String value) { return Arrays.stream(CustomFieldEnum.values()) .filter(enm -> Objects.equals(enm.getValue(), value)) .findFirst() .orElse(null); } }
- Step2 Add Enum field to corresponding BaseEntity.
@Entity
@Getter
@Setter
@NoArgsConstructor
public class MyEntity extends BaseEntity {
@Enumerated(value = EnumType.STRING)
@Column
private CustomFieldEnum customField;
}
- Step3 Add Enum field to corresponding DataResponseDTO.
@Getter
@Setter
@NoArgsConstructor
public class MyExampleDTO extends DataResponseDTO {
@SearchParameter(name = "customField", provider = EnumValueProvider.class)
private CustomFieldEnum customField;
public MyExampleDTO(MyEntity entity) {
this.id = entity.getId().toString();
this.customField = entity.getCustomField();
}
}
-
Step4 Use fields.setEnumValues in the appropriate FieldMetaBuilder to ensure the frontend reseives the list of values in the /row-meta method under "values" tag.
If the values list is dependent on a parent field, use fields.setEnumValues within the buildRowDependentMeta method to dynamically set it based on the parent.
@Override
public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
if (configuration.getForceActiveEnabled()) {
fields.setForceActive(MyExampleDTO_.customField);
}
fields.setEnumValues(MyExampleDTO_.customField, CustomFieldEnum.values());
}
- Step5 Add to .widget.json.
- Step5 Add to .widget.json.
- Step5 Add to .widget.json.
(since release 2.0.9)
Step 1. Configurable dictionary. Add description and value dictionary to DICTIONARY.csv.
TYPE;KEY;VALUE;DISPLAY_ORDER;DESCRIPTION;ACTIVE;ID
REGIONS;MOSCOW;Moscow;;;;
REGIONS;SAINT PETERBURG;St. Petersburg;;;;
REGIONS;KOSTROMA;Kostroma;1;;;
REGIONS;SYKTYVKAR;Syktyvkar;2;;;
REGIONS;NewYork;New York;3;;;
Step 2. Create record = name type dictionary implements Dictionary (If a dictionary type includes underscores, its name is transformed into CamelCase by removing the underscores and capitalizing the first letter of each word.)
public record Regions(String key) implements Dictionary {
public static final Regions SAINT_PETERBURG = new Regions("SAINT_PETERBURG");
public static final Regions MOSCOW = new Regions("MOSCOW");
}
Step 3. Add field with new record to corresponding BaseEntity.
@Entity
@Getter
@Setter
@NoArgsConstructor
public class MyEntity extends BaseEntity {
@Column
private String customField;
@Column
private Regions customFieldDictionary;
}
Step 4. Add field with new record to corresponding DataResponseDTO.
@Getter
@Setter
@NoArgsConstructor
public class MyExampleDTO extends DataResponseDTO {
@SearchParameter(name = "customField", provider = StringValueProvider.class)
private String customField;
@SearchParameter(name = "customFieldDictionary", provider = DictionaryValueProvider.class)
private Regions customFieldDictionary;
public MyExampleDTO(MyEntity entity) {
this.id = entity.getId().toString();
this.customField = entity.getCustomField();
this.customFieldDictionary = entity.getCustomFieldDictionary();
}
}
Step 5. Use fields.setDictionaryValues in the appropriate FieldMetaBuilder to ensure the frontend reseives the list of values in the /row-meta method under "values" tag.
If the values list is dependent on a parent field, use fields.setEnumValues within the buildRowDependentMeta method to dynamically set it based on the parent.
@Override
public void buildRowDependentMeta(RowDependentFieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription,
Long id, Long parentId) {
fields.setEnabled(MyExampleDTO_.customField);
fields.setEnabled(MyExampleDTO_.customFieldDictionary);
fields.setDictionaryValues(MyExampleDTO_.customFieldDictionary);
}
Step 6. Add fields.setDictionaryFilterValues to corresponding FieldMetaBuilder.
The front-end requires us to display all directory data within the method /row-meta tag values. If the values list is dependent on the parent, we should use the buildIndependentMeta method for this purpose.
@Override
public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
fields.setDictionaryFilterValues(MyExampleDTO_.customFieldDictionary);
}
Step 7. Add to .widget.json.
Step 7. Add to .widget.json.
{
"name": "MyExampleInfo",
"title": "Info configurable dictionary basic",
"type": "Info",
"bc": "myexample",
"fields": [
{
"label": "Custom Field Lov",
"key": "customFieldDictionary",
"type": "dictionary"
},
{
"label": "customField",
"key": "customField",
"type": "input"
}
],
"options": {
"layout": {
"rows": [
{
"cols": [
{
"fieldKey": "customFieldDictionary",
"span": 12
}
]
},
{
"cols": [
{
"fieldKey": "customField",
"span": 12
}
]
}
]
}
}
}
Step 7. Add to .widget.json.
{
"name": "MyExampleForm",
"title": "Form configurable dictionary basic",
"type": "Form",
"bc": "myexample",
"fields": [
{
"label": "Custom Field Lov",
"key": "customFieldDictionary",
"type": "dictionary"
},
{
"label": "customField",
"key": "customField",
"type": "input"
}
],
"options": {
"layout": {
"rows": [
{
"cols": [
{
"fieldKey": "customFieldDictionary",
"span": 12
}
]
},
{
"cols": [
{
"fieldKey": "customField",
"span": 12
}
]
}
]
}
}
}
Step 8. Add bean DictionaryProvider. Incorporate it into the application a single time.
/*
* © OOO "SI IKS LAB", 2022-2023
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@Configuration
public class DictionaryConfig {
@Bean
public DictionaryProvider dictionaryProvider() {
return new DictionaryProvider() {
@Override
public <T extends Dictionary> T lookupName(@NonNull Class<T> type, @NonNull DictionaryValue value) {
var dictTmp = Dictionary.of(type, "");
var lov = DictionaryCache.dictionary().lookupName(value.getValue(), dictTmp.getDictionaryType());
return Dictionary.of(type, lov.getKey());
}
@Override
public <T extends Dictionary> SimpleDictionary lookupValue(@NonNull T dictionary) {
return DictionaryCache.dictionary().get(dictionary.getDictionaryType(), dictionary.key());
}
@Override
public <T extends Dictionary> Collection<T> getAll(@NonNull Class<T> dictionaryType) {
return DictionaryCache.dictionary().getAll(Dictionary.of(dictionaryType, "").getDictionaryType())
.stream()
.map(e -> Dictionary.of(dictionaryType, e.getKey()))
.toList();
}
};
}
}
-
Step 1. LOV Create LOV
-
Step 1.1 Add type LOV to CXBOX-DICTIONARY_TYPE.csv.
-
Step 1.2 Add description and value LOV to CXBOX-DICTIONARY_ITEM.csv.
-
Step 1.3 Add corresponding launguage to database change management DICTIONARY_ITEM_TR.
<changeSet id="ADD LANGUAGE DICTIONARY_ITEM_TR" author="initial"> <sql> insert into DICTIONARY_ITEM_TR (ID, LANGUAGE, VALUE) select ID, 'en' as LANGUAGE, VALUE as VALUE from DICTIONARY_ITEM; </sql> <sql> insert into DICTIONARY_ITEM_TR (ID, LANGUAGE, VALUE) select ID, 'ru' as LANGUAGE, VALUE as VALUE from DICTIONARY_ITEM; </sql> </changeSet>
-
Step 1.4 Add in project AdministeredDictionary
-
Step 1.5 Add in project AdministeredDictionaryType
@Getter @RequiredArgsConstructor public enum AdministeredDictionaryType implements Serializable, IDictionaryType { @Override public LOV lookupName(String val) { return dictionary().lookupName(val, this); } @Override public String lookupValue(LOV lov) { return dictionary().lookupValue(lov, this); } @Override public String getName() { return name(); } public boolean containsKey(String key) { return dictionary().containsKey(key, this); } }
-
Step 1.6 Add LOV (REGIONS) in AdministeredDictionaryType
@Getter @RequiredArgsConstructor public enum AdministeredDictionaryType implements Serializable, IDictionaryType { REGIONS; @Override public LOV lookupName(String val) { return dictionary().lookupName(val, this); } @Override public String lookupValue(LOV lov) { return dictionary().lookupValue(lov, this); } @Override public String getName() { return name(); } public boolean containsKey(String key) { return dictionary().containsKey(key, this); } }
-
-
Step2 Add LOV field to corresponding BaseEntity.
@Entity
@Getter
@Setter
@NoArgsConstructor
public class MyEntity extends BaseEntity {
@Column
private LOV customField;
}
- Step3Add String field to corresponding DataResponseDTO.
@Getter
@Setter
@NoArgsConstructor
public class MyExampleDTO extends DataResponseDTO {
@SearchParameter(provider = LovValueProvider.class)
@AdministeredDictionaryOld(REGIONS)
private String customField;
public MyExampleDTO(MyEntity entity) {
this.id = entity.getId().toString();
this.customField = REGIONS.lookupValue(entity.getCustomField());
}
}
- Step4 Add fields.setDictionaryTypeWithAllValues to corresponding FieldMetaBuilder.
The front-end requires us to display all directory data within the method /row-meta tag values. If the values list is dependent on the parent, we should use the buildRowDependentMeta method for this purpose.
@Override
public void buildRowDependentMeta(RowDependentFieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription,
Long id, Long parentId) {
fields.setEnabled(MyExampleDTO_.customField);
}
- Step5 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
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");
}
Color
Color
allows you to specify a field color. It can be calculated based on business logic of application
Calculated color
Constant color
How does it look?
not applicable
How to add?
Example
Step 1 Add custom field for color
to corresponding DataResponseDTO. The field can contain a HEX color or be null.
@Getter
@Setter
@NoArgsConstructor
public class MyExampleDTO extends DataResponseDTO {
@SearchParameter(name = "customField", provider = EnumValueProvider.class)
private CustomFieldEnum customField;
private String customFieldColor;
public MyExampleDTO(MyEntity entity) {
this.id = entity.getId().toString();
this.customField = entity.getCustomField();
this.customFieldColor = "#edaa";
}
}
Step 2 Add "bgColorKey" : custom field for color
to .widget.json.
Step 2 Add "bgColorKey" : custom field for color
to .widget.json.
{
"name": "MyExampleInfo",
"title": "Info title",
"type": "Info",
"bc": "myExampleBc",
"fields": [
{
"label": "Custom Field",
"key": "customField",
"type": "dictionary",
"bgColorKey": "customFieldColor"
}
],
"options": {
"layout": {
"rows": [
{
"cols": [
{
"fieldKey": "customField",
"span": 12
}
]
}
]
}
}
}
not applicable
Add "bgColor" : HEX color
to .widget.json.
Add "bgColor" : HEX color
to .widget.json.
not applicable
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));
}
Step2 Add fields.setEnabled to corresponding FieldMetaBuilder.
@Override
public void buildRowDependentMeta(RowDependentFieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription,
Long id, Long parentId) {
fields.setEnabled(MyExampleDTO_.customField);
}
Option 1 Enabled by default.
@Override
public void buildRowDependentMeta(RowDependentFieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription,
Long id, Long parentId) {
}
Option 2 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
Enum Live Sample · GitHub
Dictionary Live Sample · GitHub
Filtering
allows you to search data based on criteria. Search uses in
operator.
How does it look?
not applicable
not applicable
How to add?
Example
Step 1 Add @SearchParameter to corresponding DataResponseDTO. (Advanced customization SearchParameter)
@Getter
@Setter
@NoArgsConstructor
public class MyExampleDTO extends DataResponseDTO {
@SearchParameter(name = "customField", provider = EnumValueProvider.class)
private CustomFieldEnum customField;
public MyExampleDTO(MyEntity entity) {
this.id = entity.getId().toString();
this.customField = entity.getCustomField();
}
}
Step 2 Add fields.enableFilter to corresponding FieldMetaBuilder.
Add fields.setEnumFilterValues to corresponding FieldMetaBuilder.
The front-end requires us to display all directory data within the method /row-meta tag filterValues
.
@Override
public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
if (configuration.getForceActiveEnabled()) {
fields.setForceActive(MyExampleDTO_.customField);
}
fields.setEnumValues(MyExampleDTO_.customField, CustomFieldEnum.values());
fields.setEnumFilterValues(fields, MyExampleDTO_.customField, CustomFieldEnum.values());
fields.enableFilter(MyExampleDTO_.customField);
fields.enableSort(MyExampleDTO_.customField);
}
not applicable
not applicable
Step 1 Add @SearchParameter to corresponding DataResponseDTO. (Advanced customization SearchParameter)
@Getter
@Setter
@NoArgsConstructor
public class MyExampleDTO extends DataResponseDTO {
@SearchParameter(name = "customField", provider = StringValueProvider.class)
private String customField;
@SearchParameter(name = "customFieldDictionary", provider = DictionaryValueProvider.class)
private CustomDictionaryFiltration customFieldDictionary;
public MyExampleDTO(MyEntity entity) {
this.id = entity.getId().toString();
this.customField = entity.getCustomField();
}
}
Step 2 Add fields.enableFilter to corresponding FieldMetaBuilder.
Add fields.setDictionaryFilterValues to corresponding FieldMetaBuilder.
The front-end requires us to display all directory data within the method /row-meta tag filterValues
.
not applicable
not applicable
Drilldown
DrillDown
allows you to navigate to another view by simply tapping on it. Target view and other drill-down parts can be calculated based on business logic of application
Also, it optionally allows you to filter data on target view before it will be opened see more
DrillDown
How does it look?
not applicable
How to add?
Example
Option 1
Step 1
Add fields.setDrilldown to corresponding FieldMetaBuilder.
@Override
public void buildRowDependentMeta(RowDependentFieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription,
Long id, Long parentId) {
fields.setEnabled(MyExampleDTO_.customField);
fields.setDrilldown(
MyExampleDTO_.customField,
DrillDownType.INNER,
"/screen/myexample/view/myexampleform/" + PlatformMyExampleController.myExampleBc + "/" + id
);
}
Step 2
Add "drillDown": "true" to .widget.json.
{
"name": "MyExampleList",
"title": "List title",
"type": "List",
"bc": "myExampleBc",
"fields": [
{
"title": "Custom Field",
"key": "customField",
"type": "dictionary",
"drillDown": "true"
}
]
}
Option 2
Add "drillDownKey" : custom field
to .widget.json. See more Drilldown
[:material-play-circle: Live Sample](http://code-samples.cxbox.org/ui/#/screen/myexample86){:target="_blank"} ·
[:fontawesome-brands-github: GitHub](https://github.com/CX-Box/cxbox-code-samples/tree/main/src/main/java/org/demo/documentation/fields/dictionary/drilldown){:target="_blank"}
Step 2
Add "drillDown": "true" to .widget.json.
{
"name": "MyExampleInfo",
"title": "Info title",
"type": "Info",
"bc": "myExampleBc",
"fields": [
{
"label": "Custom Field",
"key": "customField",
"type": "dictionary",
"drillDown": "true"
}
],
"options": {
"layout": {
"rows": [
{
"cols": [
{
"fieldKey": "customField",
"span": 12
}
]
}
]
}
}
}
custom field
to .widget.json. See more Drilldown
not applicable
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
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)) {
if (data.getCustomField() != null && !CustomFieldEnum.HIGH.getValue().equals(data.getCustomField().getValue())) {
throw new BusinessException().addPopup(ONLY_HIGH);
}
entity.setCustomField(data.getCustomField());
}
return new ActionResultDTO<>(entityToDto(bc, entity));
}
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));
}
Add PreAction.confirm to corresponding VersionAwareResponseService.
@Override
public Actions<MyExampleDTO> getActions() {
return Actions.<MyExampleDTO>builder()
.action(act -> act
.action("save", "save")
.withPreAction(PreAction.confirm("You want to save the value ?"))
)
.build();
}
Add javax.validation to corresponding DataResponseDTO.
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", provider = EnumValueProvider.class)
@NotNull(message = "Custom message about error")
private CustomFieldEnum customField;
public MyExampleDTO(MyEntity entity) {
this.id = entity.getId().toString();
this.customField = entity.getCustomField();
}
}
Create сustom service for business logic check.
Use if:
Business logic check required for fields
Step 1
Create сustom method for check.
private void validateFields(BusinessComponent bc, MyExampleDTO dto) {
BusinessError.Entity entity = new BusinessError.Entity(bc);
if (!dto.getCustomField().getValue().equals(CustomFieldEnum.HIGH.getValue())) {
entity.addField(MyExampleDTO_.customField.getName(), "Custom message about error");
}
if (!dto.getCustomFieldAdditional().getValue().equals(CustomFieldEnum.HIGH.getValue())) {
entity.addField(MyExampleDTO_.customFieldAdditional.getName(), "Custom message about error");
}
if (entity.getFields().size() > 0) {
throw new BusinessException().setEntity(entity);
}
}
Step 2
Add сustom method for check to corresponding VersionAwareResponseService.
Sorting
Sorting
allows you to sort data in ascending or descending order.
Sort by key value.
How does it look?
not applicable
not applicable
How to add?
Example
Step 1
Add fields.enableFilter to corresponding buildIndependentMeta FieldMetaBuilder.
@Override
public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
if (configuration.getForceActiveEnabled()) {
fields.setForceActive(MyExampleDTO_.customField);
}
fields.setEnumValues(MyExampleDTO_.customField, CustomFieldEnum.values());
fields.setEnumFilterValues(fields, MyExampleDTO_.customField, CustomFieldEnum.values());
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.
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);
}
Additional properties
Icon
(since release 2.0.8)
Icons are small graphic symbols used within a user interface to visually represent certain actions, fields, or data types, making the interface more intuitive and easier to navigate. They serve as visual cues to help users quickly identify the purpose of a field without needing to read a title.
Basic
Tips
We recommend avoiding long values for value fields, as filtering sends a string with vallue to the backend, which may be subject to length limitations.
There are two display types icons:
There are two display modes:
Default Mode
(icon and text)Icon-Only Mode
(only icon)
Modes
Default Mode
Both icon and text are always shown.
Applies to:
- Drop-down directory
- Edit mode/Creation
- Filtering
How does it look?
How to add?
Example
The frontend display mechanism for icons works as follows:
Icon Value Matching: The frontend needs to have all occurrences of the icon values within a comprehensive tag collection (allValues).
Icon Retrieval: It then takes the specified value from values(see more how_to_add)/filterValues(see more how_to_add and searches for a matching icon value within allValues. This process ensures that icons are displayed based on the specified icon values.
Step 1
Add fields.setAllValuesWithIcons to buildIndependentMeta to corresponding FieldMetaBuilder.
@Override
public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
fields.enableFilter(MyExampleDTO_.customFieldMultivalueModeIcon);
fields.enableFilter(MyExampleDTO_.customFieldMultivalue);
fields.enableFilter(MyExampleDTO_.customFieldDictionaryInlinePickList);
fields.enableFilter(MyExampleDTO_.customFieldPickList);
fields.enableFilter(MyExampleDTO_.customFieldDictionary);
fields.enableSort(MyExampleDTO_.customFieldDictionary);
fields.setEnumFilterValues(fields,MyExampleDTO_.customFieldPickList,CustomFieldDictionaryEnum.values());
fields.setEnumFilterValues(fields,MyExampleDTO_.customFieldDictionary,CustomFieldDictionaryEnum.values());
fields.setAllValuesWithIcons(MyExampleDTO_.customFieldDictionary, CustomFieldDictionaryEnum.iconMap());
fields.setEnumValues(MyExampleDTO_.customFieldDictionary, CustomFieldDictionaryEnum.values());
}
Step 2
(optional)
Missing mod tag = "mode": "default
Add "mode": "default" to corresponding widget.json.
Step 1
Add fields.setDictionaryValues to corresponding FieldMetaBuilder.
@Override
public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
fields.setDictionaryFilterValues(MyExampleDTO_.customFieldDictionary);
fields.setDictionaryIcons(MyExampleDTO_.customFieldDictionary,CustomDictionary.icons);
}
Step 2
(optional)
Missing mod tag = "mode": "default
Add "mode": "default" to corresponding widget.json.
Step 1
Add fields.setDictionaryTypeWithAllValues to corresponding FieldMetaBuilder.
@Override
public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
fields.enableFilter(MyExampleDTO_.customFieldDictionary);
fields.enableSort(MyExampleDTO_.customFieldDictionary);
fields.setDictionaryTypeWithAllValues(MyExampleDTO_.customFieldDictionary, REGIONS);
Map<LOV, Icon> valueIconMap = Map.of(
MOSCOW, ARROW_UP,
SAINT_PETERBURG, DOWN,
SYKTYVKAR, ARROW_UP_BLUE,
KOSTROMA, WATERMELON);
fields.setAllValuesWithIcons(MyExampleDTO_.customFieldDictionary, REGIONS, valueIconMap);
fields.setAllFilterValuesByLovType(MyExampleDTO_.customFieldDictionary, REGIONS);
}
Step 2
Missing mod tag = "mode": "default
Add "mode": "default" to corresponding widget.json.
Icon Mode
Only the icon is displayed.
When hovering over the icon, a tooltip appears displaying text that is retrieved from the value.
Applies to:
- Drop-down directory
- Edit mode/Creation
- For filtering, icon and text are always shown.
How does it look?
How to add?
Example
The frontend display mechanism for icons works as follows:
allValues
tag is filled with values from the icon-to-value mapping directory.
Next, we retrieve the value from either values (see how_to_add) or filterValues (see how_to_add)
and then search for the matching icon in allValues.
Step 1
Add fields.setAllValuesWithIcons to corresponding FieldMetaBuilder.
@Override
public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
fields.enableFilter(MyExampleDTO_.customFieldMultivalueModeIcon);
fields.enableFilter(MyExampleDTO_.customFieldMultivalue);
fields.enableFilter(MyExampleDTO_.customFieldDictionaryInlinePickList);
fields.enableFilter(MyExampleDTO_.customFieldPickList);
fields.enableFilter(MyExampleDTO_.customFieldDictionary);
fields.enableSort(MyExampleDTO_.customFieldDictionary);
fields.setEnumFilterValues(fields,MyExampleDTO_.customFieldPickList,CustomFieldDictionaryEnum.values());
fields.setEnumFilterValues(fields,MyExampleDTO_.customFieldDictionary,CustomFieldDictionaryEnum.values());
fields.setAllValuesWithIcons(MyExampleDTO_.customFieldDictionary, CustomFieldDictionaryEnum.iconMap());
fields.setEnumValues(MyExampleDTO_.customFieldDictionary, CustomFieldDictionaryEnum.values());
}
Step 2
Add "mode": "icon" to corresponding widget.json.
Step 1
Add fields.setDictionaryIcons to corresponding FieldMetaBuilder.
@Override
public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
fields.setDictionaryFilterValues(MyExampleDTO_.customFieldDictionary);
fields.setDictionaryIcons(MyExampleDTO_.customFieldDictionary,CustomDictionary.icons);
}
Step 2
Add "mode": "icon" to corresponding widget.json.
Types icons
The workflow logic is to first search for the icon name in the custom icons folder. If the icon is found, it will be used from there; if not, the search will continue in the standard Ant icons folder.
Icon selection logic:
- Search by icon name in the custom icons folder.
- If found, use the custom icon.
- If not found, look in the standard Ant icons folder and use the icon from there.
Standard icons
Cxbox-ui already includes this icon library.
Ant Design icons You can customize the color of the standard icon using a hex color code.
How does it look?
How to add?
Example
Simply copy the icon name and pass it to the method—that's all you need.
For example, add
Add standart icon to corresponding Icon. ARROW_UP("arrow-up")
Step 1
Add standart icon to corresponding Enum with icons.
ARROW_UP("arrow-up")
Add standart icon and hex color code to corresponding Icon.
ARROW_UP_BLUE("arrow-up #0cbfe9")
Step 1
Add standart icon and hex color code to corresponding Enum with icons.
ARROW_UP_BLUE("arrow-up #0cbfe9")
Step 1
Add standart icon and hex color code to corresponding Enum with icons.
ARROW_UP_BLUE("arrow-up #0cbfe9")
Custom icons
Custom icons can be uploaded. Icons should be uploaded in SVG format.
How does it look?
How to add?
Example
Step 1
Add icon (watermelon.svg) in folder ui/src/assets/icons/dictionaryCustomIcons
Step 2
Create a link to the icon file by adding a reference to ui/src/assets/icons/dictionaryCustomIcons/index.ts
Step 3
Add custom icon to corresponding Icon.
WATERMELON("watermelon")
Administration dictionary
(since release 2.0.9)
cxbox/core 4.0.0-M12
This screen allows you to edit and create dictionaries .
To apply the changes, click the "Clear Cache" button on the administration screen and refresh the page on the user screen to re-request the data.
How does it look?
How to add?
Example
-
Step 1. Create DTO with core entity DictionaryItem
@Getter @Setter @NoArgsConstructor public class DictionaryItemDTO extends DataResponseDTO { @SearchParameter private String key; @SearchParameter private String value; @SearchParameter(provider = BooleanValueProvider.class) private Boolean active; @SearchParameter private String type; @SearchParameter(name = "dictionaryTypeId.id", provider = LongValueProvider.class) private Long dictionaryTypeId; @SearchParameter(provider = BigDecimalValueProvider.class) private Integer displayOrder; @SearchParameter private String description; public DictionaryItemDTO(DictionaryItem dictionaryItem) { this.id = dictionaryItem.getId().toString(); this.dictionaryTypeId = Optional.ofNullable(dictionaryItem.getDictionaryTypeId()).map(BaseEntity::getId).orElse(null); this.key = dictionaryItem.getKey(); this.value = dictionaryItem.getValue(); this.active = dictionaryItem.isActive(); this.type = dictionaryItem.getType(); this.displayOrder = dictionaryItem.getDisplayOrder(); this.description = dictionaryItem.getDescription(); } }
-
Step2 Create FieldMetaBuilder
@Service public class MyExampleMeta extends FieldMetaBuilder<DictionaryItemDTO> { // --8<-- [start:buildRowDependentMeta] @Override public void buildRowDependentMeta(RowDependentFieldsMeta<DictionaryItemDTO> fields, InnerBcDescription bcDescription, Long id, Long parentId) { fields.setEnabled(DictionaryItemDTO_.active); fields.setEnabled(DictionaryItemDTO_.description); fields.setEnabled(DictionaryItemDTO_.displayOrder); fields.setEnabled(DictionaryItemDTO_.value); fields.setEnabled(DictionaryItemDTO_.key); fields.setEnabled(DictionaryItemDTO_.type); } // --8<-- [end:buildRowDependentMeta] // --8<-- [start:buildIndependentMeta] @Override public void buildIndependentMeta(FieldsMeta<DictionaryItemDTO> fields, InnerBcDescription bcDescription, Long parentId) { fields.enableFilter(DictionaryItemDTO_.active); fields.enableFilter(DictionaryItemDTO_.description); fields.enableFilter(DictionaryItemDTO_.displayOrder); fields.enableFilter(DictionaryItemDTO_.value); fields.enableFilter(DictionaryItemDTO_.key); fields.enableFilter(DictionaryItemDTO_.type); } // --8<-- [end:buildIndependentMeta] }
-
Step3 Create VersionAwareResponseService
Add buttons:
-
clear-cache
To apply changes to the dictionary clearing the cache is required. This will not work in cluster (>1 app nodes).Please, add scheduler or other mechanism to clear cache in cluster -
export_liquibase
Button that allows users to upload reference books in the csv format.
@SneakyThrows @NotNull private FileDownloadDto toCsv() { String name = "DICTIONARY.csv"; var header = List.of("TYPE", "KEY", "VALUE", "DISPLAY_ORDER", "DESCRIPTION", "ACTIVE", "ID"); var body = repository.findAll().stream() .sorted(Comparator.comparing(DictionaryItem::getType) .thenComparing(dictionaryItem -> Optional.ofNullable(dictionaryItem.getDisplayOrder()).orElse(Integer.MAX_VALUE)) .thenComparing(DictionaryItem::getValue) .thenComparing(DictionaryItem::getId) ) .map(e -> List.of( e.getType(), e.getKey(), e.getValue(), Optional.ofNullable(e.getDisplayOrder()).map(d -> "" + d).orElse(""), e.getDescription(), e.isActive() ? "" : "false", "" )); return CSVUtils.toCsv(header, body, name, ";"); }
Add validate in
updateEntity
:private void validate(BusinessComponent bc, ActionResultDTO<DictionaryItemDTO> result) { try { repository.flush(); } catch (DataIntegrityViolationException e) { if (e.getCause() instanceof ConstraintViolationException uniqEx) { if (DictionaryItem.CONSTRAINT_UNIQ_TYPE_KEY.equalsIgnoreCase(uniqEx.getConstraintName())) { throw new BusinessException(e).setEntity(new BusinessError.Entity(bc).addField( DictionaryItem_.key.getName(), "Key already exists for type " + result.getRecord().getType())); } if (DictionaryItem.CONSTRAINT_UNIQ_TYPE_VALUE.equalsIgnoreCase(uniqEx.getConstraintName())) { throw new BusinessException(e).setEntity(new BusinessError.Entity(bc).addField( DictionaryItem_.value.getName(), "Value already exists for type " + result.getRecord().getType())); } } throw e; } }
@Service public class MyExampleService extends VersionAwareResponseService<DictionaryItemDTO, DictionaryItem> { @Autowired private DictionaryCache dictionaryCache; private final MyEntityRepository repository; @Autowired private EntityManager entityManager; @Autowired private LocaleService localeService; @Autowired private CxboxFileService cxboxFileService; public MyExampleService(MyEntityRepository repository) { super(DictionaryItemDTO.class, DictionaryItem.class, null, MyExampleMeta.class); this.repository = repository; } @Override protected CreateResult<DictionaryItemDTO> doCreateEntity(DictionaryItem entity, BusinessComponent bc) { entity.setActive(true); entity.setTranslations(localeService.getSupportedLanguages().stream().collect(Collectors.toMap( lang -> lang, lang -> new DictionaryItemTranslation(new TranslationId(lang), entity, null) ))); repository.save(entity); return new CreateResult<>(entityToDto(bc, entity)); } @Override protected ActionResultDTO<DictionaryItemDTO> doUpdateEntity(DictionaryItem entity, DictionaryItemDTO data, BusinessComponent bc) { setIfChanged(data, DictionaryItemDTO_.type, entity::setType); if (data.isFieldChanged(DictionaryItemDTO_.dictionaryTypeId)) { entity.setDictionaryTypeId(data.getDictionaryTypeId() != null ? entityManager.getReference(DictionaryTypeDesc.class, data.getDictionaryTypeId()) : null); } setIfChanged(data, DictionaryItemDTO_.key, entity::setKey); if (data.isFieldChanged(DictionaryItemDTO_.value)) { entity.setValue(data.getValue()); entity.getTranslations().forEach((lang, tr) -> tr.setValue(data.getValue())); } setIfChanged(data, DictionaryItemDTO_.active, entity::setActive); setIfChanged(data, DictionaryItemDTO_.displayOrder, entity::setDisplayOrder); setIfChanged(data, DictionaryItemDTO_.description, entity::setDescription); return new ActionResultDTO<>(entityToDto(bc, entity)); } // --8<-- [start:updateEntity] @Override public ActionResultDTO<DictionaryItemDTO> updateEntity(BusinessComponent bc, DataResponseDTO data) { var result = super.updateEntity(bc, data); validate(bc, result); return result; } // --8<-- [end:updateEntity] // --8<-- [start:getActions] @Override public Actions<DictionaryItemDTO> getActions() { return Actions.<DictionaryItemDTO>builder() .action(act -> act .action("clear-cache", "Clear Cache") .scope(ActionScope.BC) .invoker((bc, data) -> { // This will not work in cluster (>1 app nodes). // Please, add scheduler or other mechanism to clear cache in cluster dictionaryCache.reload(); return new ActionResultDTO<>(); })) .action(act -> act .action("export_liquibase", "Export") .scope(ActionScope.BC) .invoker((data, bc) -> new ActionResultDTO<DictionaryItemDTO>() .setAction(PostAction.downloadFile(cxboxFileService.upload(toCsv(), null)))) ) .create(crt -> crt.text("Create")) .cancelCreate(ccr -> ccr.text("Cancel")) .save(sv -> sv.text("Save")) .delete(dlt -> dlt.text("Delete")) .build(); } // --8<-- [end:getActions] // --8<-- [start:validate] private void validate(BusinessComponent bc, ActionResultDTO<DictionaryItemDTO> result) { try { repository.flush(); } catch (DataIntegrityViolationException e) { if (e.getCause() instanceof ConstraintViolationException uniqEx) { if (DictionaryItem.CONSTRAINT_UNIQ_TYPE_KEY.equalsIgnoreCase(uniqEx.getConstraintName())) { throw new BusinessException(e).setEntity(new BusinessError.Entity(bc).addField( DictionaryItem_.key.getName(), "Key already exists for type " + result.getRecord().getType())); } if (DictionaryItem.CONSTRAINT_UNIQ_TYPE_VALUE.equalsIgnoreCase(uniqEx.getConstraintName())) { throw new BusinessException(e).setEntity(new BusinessError.Entity(bc).addField( DictionaryItem_.value.getName(), "Value already exists for type " + result.getRecord().getType())); } } throw e; } } // --8<-- [end:validate] // --8<-- [start:toCsv] @SneakyThrows @NotNull private FileDownloadDto toCsv() { String name = "DICTIONARY.csv"; var header = List.of("TYPE", "KEY", "VALUE", "DISPLAY_ORDER", "DESCRIPTION", "ACTIVE", "ID"); var body = repository.findAll().stream() .sorted(Comparator.comparing(DictionaryItem::getType) .thenComparing(dictionaryItem -> Optional.ofNullable(dictionaryItem.getDisplayOrder()).orElse(Integer.MAX_VALUE)) .thenComparing(DictionaryItem::getValue) .thenComparing(DictionaryItem::getId) ) .map(e -> List.of( e.getType(), e.getKey(), e.getValue(), Optional.ofNullable(e.getDisplayOrder()).map(d -> "" + d).orElse(""), e.getDescription(), e.isActive() ? "" : "false", "" )); return CSVUtils.toCsv(header, body, name, ";"); } // --8<-- [end:toCsv] }
-
-
Step4 Create PickListPopup with types for dictionary fields.
- Step 4.1 Create DTO with core entity DictionaryTypeDesc.
@Getter @Setter @NoArgsConstructor public class DictionaryTypeDescPickDTO extends DataResponseDTO { private String type; public DictionaryTypeDescPickDTO(DictionaryTypeDesc entity) { this.id = entity.getId().toString(); this.type = entity.getType(); } }
-
Step 4.2 Add business component to corresponding EnumBcIdentifier.
myexample(MyExampleService.class), dictionaryTypeDescPick(myexample, DictionaryTypeDescPickService.class); public static final EnumBcIdentifier.Holder<CxboxMyExampleController> Holder = new Holder<>( CxboxMyExampleController.class); private final BcDescription bcDescription; CxboxMyExampleController(String parentName, Class<?> serviceClass, boolean refresh) { this.bcDescription = buildDescription(parentName, serviceClass, refresh); } CxboxMyExampleController(BcIdentifier parent, Class<?> serviceClass, boolean refresh) { this(parent == null ? null : parent.getName(), serviceClass, refresh); } CxboxMyExampleController(BcIdentifier parent, Class<?> serviceClass) { this(parent, serviceClass, false); } CxboxMyExampleController(Class<?> serviceClass) { this((String) null, serviceClass, false); } @Component public static class BcSupplier extends AbstractEnumBcSupplier<CxboxMyExampleController> { public BcSupplier() { super(CxboxMyExampleController.Holder); } } }
-
Step 4.3 Create FieldMetaBuilder
@SuppressWarnings("EmptyMethod") @Service public class DictionaryTypeDescPickMeta extends FieldMetaBuilder<DictionaryTypeDescPickDTO> { @Override public void buildRowDependentMeta(RowDependentFieldsMeta<DictionaryTypeDescPickDTO> fields, InnerBcDescription bcDescription, Long id, Long parentId) { fields.setEnabled(DictionaryTypeDescPickDTO_.id); fields.setEnabled(DictionaryTypeDescPickDTO_.type); } @Override public void buildIndependentMeta(FieldsMeta<DictionaryTypeDescPickDTO> fields, InnerBcDescription bcDescription, Long parentId) { } }
-
Step 4.4 Create
PickListPopup
widget.json
-
Step5 Create administration widget.json
{ "title": "Type", "key": "type", "type": "pickList", "popupBcName": "dictionaryTypeDescPick", "pickMap": { "type": "type", "id": "id" } }
`json
-
Step6 Add administration widget to corresponding view.json
{ "name": "myexamplelist", "title": "MyExample List", "template": "DashboardView", "url": "/screen/myexample/view/myexamplelist", "widgets": [ { "widgetName": "SecondLevelMenu", "position": 1, "gridWidth": 24 },{ "widgetName": "dictionaryTypeDescPickPickListPopup", "position": 2, "gridWidth": 24 }, { "widgetName": "MyExampleDictionaryList", "position": 10, "gridWidth": 24 }, { "widgetName": "MyExampleDictionaryForm", "position": 11, "gridWidth": 24 }, { "widgetName": "MyExampleDictionaryFormTypePopup", "position": 12, "gridWidth": 24 }, { "widgetName": "MyExampleDictionaryFormTypeForm", "position": 13, "gridWidth": 24 } ], "rolesAllowed": [ "CXBOX_USER" ] }
-
Step7 Add
PickListPopup
widget to corresponding view.json{ "name": "myexamplelist", "title": "MyExample List", "template": "DashboardView", "url": "/screen/myexample/view/myexamplelist", "widgets": [ { "widgetName": "SecondLevelMenu", "position": 1, "gridWidth": 24 },{ "widgetName": "dictionaryTypeDescPickPickListPopup", "position": 2, "gridWidth": 24 }, { "widgetName": "MyExampleDictionaryList", "position": 10, "gridWidth": 24 }, { "widgetName": "MyExampleDictionaryForm", "position": 11, "gridWidth": 24 }, { "widgetName": "MyExampleDictionaryFormTypePopup", "position": 12, "gridWidth": 24 }, { "widgetName": "MyExampleDictionaryFormTypeForm", "position": 13, "gridWidth": 24 } ], "rolesAllowed": [ "CXBOX_USER" ] }
Release
To ensure that user-made changes are not lost during the release process, follow these steps:
- Upload the Current Reference File with button export: Retrieve the format file containing the current reference books from the environment (stand) where the release will be deployed.
-
Match the Files: Compare the downloaded reference file with the release file to ensure consistency and identify any discrepancies. This will help preserve user changes.
-
Proceed with Release: After verifying that the files align correctly, continue with the deployment process, ensuring that user changes are retained in the updated system.