Skip to content

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

Live Sample · GitHub

Dictionary can configurable:

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?

img_list.gif

img_info.png

img_form.gif

How to add?

Example

Step 1 Download plugin download Intellij Plugin

Step 2 Add dictionary field to an existing form widget addfield.gif

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.
{
  "name": "MyExampleList",
  "title": "List title",
  "type": "List",
  "bc": "myExampleBc",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "dictionary"
    }
  ]
}

Live Sample · GitHub

  • Step5 Add to .widget.json.
{
  "name": "MyExampleInfo",
  "title": "Info title",
  "type": "Info",
  "bc": "myExampleBc",
  "fields": [
    {
      "label": "Custom Field",
      "key": "customField",
      "type": "dictionary"
    }
  ],
  "options": {
    "layout": {
      "rows": [
        {
          "cols": [
            {
              "fieldKey": "customField",
              "span": 12
            }
          ]
        }
      ]
    }
  }
}

Live Sample · GitHub

  • Step5 Add to .widget.json.
{
  "name": "MyExampleForm",
  "title": "Form title",
  "type": "Form",
  "bc": "myExampleBc",
  "fields": [
    {
      "label": "Custom Field",
      "key": "customField",
      "type": "dictionary"
    }
  ],
  "options": {
    "layout": {
      "rows": [
        {
          "cols": [
            {
              "fieldKey": "customField",
              "span": 12
            }
          ]
        }
      ]
    }
  }
}

Live Sample · GitHub

(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.

{
  "name": "MyExampleList",
  "title": "List configurable dictionary basic",
  "type": "List",
  "bc": "myexample",
  "fields": [
    {
      "title": "Custom Field Lov",
      "key": "customFieldDictionary",
      "type": "dictionary"
    },
    {
      "title": "customField",
      "key": "customField",
      "type": "input"
    }
  ]
}

Live Sample · GitHub

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

Live Sample · GitHub

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

Live Sample · GitHub

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.

      ID;TYPE;TYPE_DESC
      200;REGIONS;Regions
      
    • Step 1.2 Add description and value LOV to CXBOX-DICTIONARY_ITEM.csv.

      "300";200;"REGIONS";"MOSCOW";"Moscow";true;0;null
      "301";200;"REGIONS";"SAINT PETERBURG";"St. Petersburg";true;1;null
      "302";200;"REGIONS";"KOSTROMA";"Kostroma";true;2;null
      "303";200;"REGIONS";"SYKTYVKAR";"Syktyvkar";true;3;null
      
    • 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.
    {
      "name": "MyExampleInfo",
      "title": "Info title",
      "type": "Info",
      "bc": "myExampleBc",
      "fields": [
        {
          "label": "Custom Field",
          "key": "customField",
          "type": "dictionary"
        }
      ],
      "options": {
        "layout": {
          "rows": [
            {
              "cols": [
                {
                  "fieldKey": "customField",
                  "span": 12
                }
              ]
            }
          ]
        }
      }
    }

Live Sample · GitHub

  • Step5 Add to .widget.json.

Live Sample · GitHub

  • Step5 Add to .widget.json.

Live Sample · GitHub

Placeholder

Live Sample · GitHub

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?

img_plchldr_list.png

not applicable

img_plchldr_form.png

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");
    }

Works for List.

Live Sample · GitHub

not applicable

Works for Form.

Live Sample · GitHub

Color

Color allows you to specify a field color. It can be calculated based on business logic of application

Calculated color

Live Sample · GitHub

Constant color

Live Sample · GitHub

How does it look?

img_color_list.png

img_color_info.png

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.

{
  "name": "MyExampleList",
  "title": "List title",
  "type": "List",
  "bc": "myExampleBc",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "dictionary",
      "bgColorKey": "customFieldColor"
    }
  ]
}

Live Sample · GitHub

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

Live Sample · GitHub

not applicable

Add "bgColor" : HEX color to .widget.json.

{
  "name": "MyExampleList",
  "title": "List title",
  "type": "List",
  "bc": "myExampleBc",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "dictionary",
      "bgColor": "#edaa"
    }
  ]
}

Live Sample · GitHub

Add "bgColor" : HEX color to .widget.json.

{
  "name": "MyExampleInfo",
  "title": "Info title",
  "type": "Info",
  "bc": "myExampleBc",
  "fields": [
    {
      "label": "Custom Field",
      "key": "customField",
      "type": "dictionary",
      "bgColor": "#edaa"
    }
  ],
  "options": {
    "layout": {
      "rows": [
        {
          "cols": [
            {
              "fieldKey": "customField",
              "span": 12
            }
          ]
        }
      ]
    }
  }
}

Live Sample · GitHub

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?

img_edit_list.png

not applicable

img_edit_form.png

img_ro_list.png

img_ro_info.png

img_ro_form.png

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);
    }

Works for List.

Live Sample · GitHub

not applicable

Works for Form.

Live Sample · GitHub

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.

Live Sample · GitHub

Works for Info.

Live Sample · GitHub

Works for Form.

Live Sample · GitHub

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?

img_filtr_list.png

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);
    }

Live Sample · GitHub

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.

    @Override
    public void buildIndependentMeta(FieldsMeta<MyExampleDTO> fields, InnerBcDescription bcDescription, Long parentId) {
        fields.setDictionaryFilterValues(MyExampleDTO_.customFieldDictionary);
    }

Live Sample · GitHub

not applicable

not applicable

Drilldown

Live Sample · GitHub

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?

img_drilldown_list

img_drilldown_info

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
            }
          ]
        }
      ]
    }
  }
}
Option 2 Add "drillDownKey" : custom field to .widget.json. See more Drilldown

Live Sample · GitHub

not applicable

Advanced customization

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.

Live Sample · GitHub

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?

img_business_error

img_runtime_error

confirm_form

img_javax_stat_list

not applicable

img_business_error

img_runtime_error

confirm_form

img_javax_stat_form

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));
    }

Works for List.

Live Sample · GitHub

not applicable

Works for Form.

Live Sample · GitHub

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.

Live Sample · GitHub

not applicable

Works for Form.

Live Sample · GitHub

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();
    }

Works for List.

Live Sample · GitHub

not applicable

Works for Form.

Live Sample · GitHub

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();
    }

}

Works for List.

Live Sample · GitHub

not applicable

Works for Form.

Live Sample · GitHub

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.

    @Override
    protected ActionResultDTO<MyExampleDTO> doUpdateEntity(MyEntity entity, MyExampleDTO data,
            BusinessComponent bc) {
        validateFields(bc, data);
        return new ActionResultDTO<>(entityToDto(bc, entity));
    }

Live Sample · GitHub

Sorting

Live Sample · GitHub

Sorting allows you to sort data in ascending or descending order. Sort by key value.

How does it look?

img_sort_list

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);
    }

Live Sample · GitHub

not applicable

not applicable

Required

Live Sample · GitHub

Required allows you to denote, that this field must have a value provided.

How does it look?

img_req_list.png

not applicable

img_req_form.png

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.

Live Sample · GitHub

not applicable

Works for Form.

Live Sample · GitHub

Additional properties

Icon

(since release 2.0.8)

Live Sample · GitHub

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:

Modes

Default Mode

Both icon and text are always shown.

Applies to:

  • Drop-down directory
  • Edit mode/Creation
  • Filtering
How does it look?

icon_list.gif

img_icon_info.png

icon_form.gif

img_icon_addinf.png

icon_gh.gif

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.

{
  "name": "MyExampleList",
  "title": "List Mode default",
  "type": "List",
  "bc": "myexample",
  "fields": [
    {
      "title": "Custom Field Dictionary",
      "key": "customFieldDictionary",
      "type": "dictionary",
      "mode": "default"
    }
  ]
}

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.

{
  "name": "MyExampleList",
  "title": "List",
  "type": "List",
  "bc": "myexample",
  "fields": [
    {
      "title": "customField",
      "key": "customFieldDictionary",
      "type": "dictionary"
    }
  ]
}

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.

{
  "name": "MyExampleList",
  "title": "List",
  "type": "List",
  "bc": "myexample",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customFieldDictionary",
      "type": "dictionary"
    }
  ]
}

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?

icon_list_icon_mode.gif

img_icon_info_icon.png

icon_form_mode_icon.gif

img_icon_addinf_mode_icon.png

icon_gh_mode_icon.gif

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.
{
  "name": "MyExampleListIconMode",
  "title": "List Mode icon",
  "type": "List",
  "bc": "myexample",
  "fields": [
    {
      "title": "Custom Field Dictionary",
      "key": "customFieldDictionary",
      "type": "dictionary",
      "mode": "icon"
    }
  ]
}

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.

{
  "name": "MyExampleListOnlyIcon",
  "title": "List",
  "type": "List",
  "bc": "myexample",
  "fields": [
    {
      "title": "customField",
      "key": "customFieldDictionary",
      "type": "dictionary",
      "mode": "icon"
    }
  ]
}

Step 1 Add fields.setDictionaryTypeWithAllValues to corresponding FieldMetaBuilder.


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?

icon_ant.png

icon_ant_color.png

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")

@RequiredArgsConstructor
@Getter
public enum IconsEnum implements Icon {
    ARROW_UP_BLUE("arrow-up #0cbfe"),
    ARROW_UP("arrow-up"),
    WATERMELON("watermelon"),
    DOWN("down");

    private final String icon;

}

Live Sample · GitHub

Step 1 Add standart icon to corresponding Enum with icons. ARROW_UP("arrow-up")

@RequiredArgsConstructor
@Getter
public enum IconsEnum implements Icon {
    ARROW_UP_BLUE("arrow-up #0cbfe"),
    ARROW_UP("arrow-up"),
    WATERMELON("watermelon"),
    DOWN("down");

    private final String icon;

}

Live Sample · GitHub

Step 1 Add standart icon to corresponding Enum with icons. ARROW_UP("arrow-up")

@RequiredArgsConstructor
@Getter
public enum IconsEnum implements Icon {
    ARROW_UP_BLUE("arrow-up #0cbfe"),
    ARROW_UP("arrow-up"),
    WATERMELON("watermelon"),
    DOWN("down");

    private final String icon;

}

Live Sample · GitHub

Add standart icon and hex color code to corresponding Icon.

For example, add

ARROW_UP_BLUE("arrow-up #0cbfe9")

@RequiredArgsConstructor
@Getter
public enum IconsEnum implements Icon {
    ARROW_UP_BLUE("arrow-up #0cbfe"),
    ARROW_UP("arrow-up"),
    WATERMELON("watermelon"),
    DOWN("down");

    private final String icon;

}

Live Sample · GitHub

Step 1 Add standart icon and hex color code to corresponding Enum with icons.

For example, add

ARROW_UP_BLUE("arrow-up #0cbfe9")

@RequiredArgsConstructor
@Getter
public enum IconsEnum implements Icon {
    ARROW_UP_BLUE("arrow-up #0cbfe"),
    ARROW_UP("arrow-up"),
    WATERMELON("watermelon"),
    DOWN("down");

    private final String icon;

}

Live Sample · GitHub

Step 1 Add standart icon and hex color code to corresponding Enum with icons.

For example, add

ARROW_UP_BLUE("arrow-up #0cbfe9")

@RequiredArgsConstructor
@Getter
public enum IconsEnum implements Icon {
    ARROW_UP_BLUE("arrow-up #0cbfe"),
    ARROW_UP("arrow-up"),
    WATERMELON("watermelon"),
    DOWN("down");

    private final String icon;

}

Live Sample · GitHub

Custom icons

Custom icons can be uploaded. Icons should be uploaded in SVG format.

How does it look?

icon_cust.png

How to add?
Example

Step 1 Add icon (watermelon.svg) in folder ui/src/assets/icons/dictionaryCustomIcons

GitHub

Step 2 Create a link to the icon file by adding a reference to ui/src/assets/icons/dictionaryCustomIcons/index.ts

GitHub

    export { ReactComponent as watermelon } from './watermelon.svg'

Step 3 Add custom icon to corresponding Icon. WATERMELON("watermelon")

@RequiredArgsConstructor
@Getter
public enum IconsEnum implements Icon {
    ARROW_UP_BLUE("arrow-up #0cbfe"),
    ARROW_UP("arrow-up"),
    WATERMELON("watermelon"),
    DOWN("down");

    private final String icon;

}

Live Sample · GitHub

Administration dictionary

(since release 2.0.9)

cxbox/core 4.0.0-M12

Live Sample GitHub

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?

admin_dict.gif

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

      {
        "title": "dictionaryTypeDescPickPickListPopup title",
        "name": "dictionaryTypeDescPickPickListPopup",
        "type": "PickListPopup",
        "bc": "dictionaryTypeDescPick",
        "fields": [
          {
            "title": "Type",
            "key": "type",
            "type": "input"
          },
          {
            "title": "id",
            "key": "id",
            "type": "text"
          }
        ]
      }
      
  • 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.

dictionary_export.gif

  • 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.