Skip to content

AssocTreePopup

Available since v3.0.1

AssocTreePopup widget is a popup component designed to the selection of multiple values.

Info

The following features are not available for the AssocTreePopup widget in this release:

  • fully or partially expanded initial load (the tree always opens collapsed)

assoc_list.png

Basics

Live Sample · GitHub

The popup shows the records as a tree: the requirements of the Tree widget apply to the business component of the popup.

The minimal data set of a record of the popup is:

  • id — the identifier of the record.
  • parentId — the identifier of the parent record. For a root record the value is null. The backend must always return this field, and the field must be filterable, because the tree is built with the requests parentId.specified=false (root records) and parentId.equals=<id> (child records). see more Lazy load
  • a name (title) field — the value shown in the tree column.
  • isLeaf — a computed boolean flag. The default value is false. The value true means that the record has no child records, so the expand arrow is not displayed for it.

Info

The first field of the fields array is rendered as the tree column: the expand arrow and the checkbox. All the other fields are rendered as ordinary columns.

parentId and isLeaf are service fields: they are declared in the popup widget with type hidden.

options.tree

All tree specific settings are placed in options.tree of .widget.json.

Example
Property Type Default Description
parentIdFieldKey String parentId Name of the field that holds the identifier of the parent record.
isLeafFieldKey String isLeaf Name of the field that holds the flag "the record has no child records".
searchModes Array of collapse, hide ["collapse", "hide"] Modes of displaying the filtration result. The first item of the array is the active one. see Search modes
onFilterApplyNestLevel Number 0 How many levels of the navigation path are shown above a found record in collapse mode. see Search modes
insertPosition start, end — Where a newly created row is inserted inside its node. see Create
selection node, nodeAndLeaf, leaf — What the user is allowed to check. see Selection modes
confirms Array of paginationUnselect, paginationSelect ["paginationUnselect"] Confirmations shown when the selection touches a node whose records are not all loaded yet. see Selection modes

Lazy load

The tree is always loaded lazily and always opens collapsed: only the root records are requested when the popup is opened, and the child records of a node are requested when the user expands it.

Info

The operation specified is used only to select the records with an empty parent. For all the other requests the operation equals is used. This is why the field that holds the parent identifier must be filterable on the backend.

Every node keeps its own pagination state, so the records of one node are loaded page by page independently of the neighbouring nodes. see more Pagination

Example
  • Root records are requested with the filter by an empty parent:
?parentId.specified=false&_page=1&_limit=5
  • Child records of a node are requested when the node is expanded:
?parentId.equals=<id>&_page=1&_limit=5

How does it look?

assoc_list.png

not applicable

assoc_form.png

assoc_list_button.png

not applicable

not applicable

Info

The button-based association differs from the MultiValue field association in the following way:

Previously selected records are not shown.

Records selected by the user in the opened association are also not displayed as selected chips.

This behavior occurs because the button-based association is not directly tied. Instead, it is designed to add records to a table.

Recommendation If you plan to use a button-based association, you must add filtering to the opened association in order to exclude records that have already been selected and added to the table.

Value selection:

choose_assoc.png

choose_button.png

Opening the widget:

choose_assoc.png

up_assoc.png

How to add?

Example

Step1 Add field with type multivalueTree see more Fields

{
  "name": "MyExampleList",
  "title": "List",
  "type": "List",
  "bc": "myexample",
  "fields": [
    {
      "title": "Custom Field New",
      "key": "customFieldNew",
      "type": "input"
    },
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "multivalueTree",
      "displayedKey": "customFieldDisplayedKey",
      "popupBcName": "myEntityMultiAssocTreePopup",
      "assocValueKey": "customField"
    }
  ],
  "options": {
    "actionGroups": {
      "include": [
        "associate"
      ]
    },
    "layout": {
      "rows": [
        {
          "cols": [
            {
              "fieldKey": "customField",
              "span": 12
            }
          ]
        }
      ]
    }
  }
}

Step2 Create popup widget .widget.json with type = "AssocTreePopup".

For the tree to work correctly, the popup widget must contain the parentId and isLeaf fields. These fields should be configured as hidden.

{
  "title": "AssocTreePopup title",
  "name": "myEntityMultiAssocTreePopup",
  "type": "AssocTreePopup",
  "bc": "myEntityMultiAssocTreePopup",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "input"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "hidden"
    }
  ]
}

Step3 Add widget to corresponding .view.json.

{
  "name": "myexamplelist",
  "title": "MyExample Form",
  "template": "DashboardView",
  "url": "/screen/myexample/view/myexamplelist",
  "widgets": [
    {
      "widgetName": "myexampleAssoc",
      "position": 1,
      "gridWidth": 24
    },
    {
      "widgetName": "myEntityMultiAssocTreePopup",
      "position": 2,
      "gridWidth": 24
    },
    {
      "widgetName": "SecondLevelMenu",
      "position": 10,
      "gridWidth": 12
    },
    {
      "widgetName": "MyExampleList",
      "position": 20,
      "gridWidth": 12
    }
  ],
  "rolesAllowed": [
    "CXBOX_USER"
  ]
}

not applicable

Step1 Add field with type multivalueTree see more Fields

{
  "name": "MyExampleForm",
  "title": "Form",
  "type": "Form",
  "bc": "myexample",
  "fields": [
    {
      "label": "Custom Field New",
      "key": "customFieldNew",
      "type": "input"
    },
    {
      "label": "Custom Field",
      "key": "customField",
      "type": "multivalueTree",
      "displayedKey": "customFieldDisplayedKey",
      "popupBcName": "myEntityMultiAssocTreePopup",
      "assocValueKey": "customField"
    }
  ],
  "options": {
    "actionGroups": {
      "include": []
    },
    "layout": {
      "rows": [
        {
          "cols": [
            {
              "fieldKey": "customFieldNew",
              "span": 12
            }
          ]
        },
        {
          "cols": [
            {
              "fieldKey": "customField",
              "span": 12
            }
          ]
        }
      ]
    }
  }
}

Step2 Create popup widget .widget.json with type = "AssocTreePopup".

For the tree to work correctly, the popup widget must contain the parentId and isLeaf fields. These fields should be configured as hidden.

{
  "title": "AssocTreePopup title",
  "name": "myEntityMultiAssocTreePopup",
  "type": "AssocTreePopup",
  "bc": "myEntityMultiAssocTreePopup",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "input"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "hidden"
    }
  ]
}

Step3 Add widget to corresponding .view.json.

{
  "name": "myexampleform",
  "title": "MyExample Form",
  "template": "DashboardView",
  "url": "/screen/myexample/view/myexampleform",
  "widgets": [
    {
      "widgetName": "myexampleAssoc",
      "position": 1,
      "gridWidth": 24
    },
    {
      "widgetName": "myEntityMultiAssocTreePopup",
      "position": 2,
      "gridWidth": 24
    },
    {
      "widgetName": "SecondLevelMenu",
      "position": 10,
      "gridWidth": 12
    },
    {
      "widgetName": "MyExampleForm",
      "position": 20,
      "gridWidth": 24
    }
  ],
  "rolesAllowed": [
    "CXBOX_USER"
  ]
}

Step1 Add button associate to corresponding VersionAwareResponseService.

    @Override
    public Actions<MyExampleDTO> getActions() {
        return Actions.<MyExampleDTO>builder()
                .associate(ast -> ast
                        .text("Popup Assoc")
                )
                .save(sv -> sv.text("Save"))
                .create(crt -> crt)
                .delete(dlt -> dlt)
                .build();
    }
Step2 Add method doAssociate to corresponding VersionAwareResponseService.

associate

    @Override
    protected AssociateResultDTO doAssociate(List<AssociateDTO> data, BusinessComponent bc) {
        List<MyEntity> myEntityList = addNewRecords(data, bc);
        List<DataResponseDTO> collect = myEntityList.stream()
                .<DataResponseDTO>map(e -> entityToDto(bc, e))
                .toList();
        return new AssociateResultDTO(collect);
    }
method addNewRecords
    @SneakyThrows
    private List<MyEntity> addNewRecords(List<AssociateDTO> dataIds, BusinessComponent bc) {
        List<MyEntity> recordList = new ArrayList<>();
        for (AssociateDTO item : dataIds) {
            List<MyEntityMulti> listMultivalue = new ArrayList<>();
            MyEntityMulti entityChild = repositoryMulti.findById(Long.valueOf(item.getId())).orElseThrow();
            listMultivalue.add(entityChild);
            MyEntity myEntity = new MyEntity().setCustomFieldList(listMultivalue).setCustomFieldNew(entityChild.getCustomFieldNew());
            recordList.add(repository.save(myEntity));
        }
        return recordList;
    }

Step3 Create file .widget.json with type = "assoc" and name = parent bc + "Assoc"

Add existing field to assoc widget. see more Fields

{
  "title": "AssocTreePopup widget action button",
  "name": "myexampleAssoc",
  "type": "AssocTreePopup",
  "bc": "myexampleAssoc",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "input"
    },
    {
      "title": "Custom Field New",
      "key": "customFieldNew",
      "type": "input"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "hidden"
    }
  ]
}
Step4 Add a dependency on parent BC for Assoc BC for to corresponding EnumBcIdentifier

@Getter
public enum CxboxMyExampleController implements EnumBcIdentifier {


   myexample(MyExampleService.class),
   myexampleAssoc(myexample, MyEntityMultiMultivalueService.class),
   myEntityMultiAssocTreePopup(myexample, MyEntityMultiMultivalueService.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(String parentName, Class<?> serviceClass) {
       this(parentName, serviceClass, false);
   }

   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, boolean refresh) {
       this((String) null, serviceClass, refresh);
   }

   CxboxMyExampleController(Class<?> serviceClass) {
       this((String) null, serviceClass, false);
   }

   @Component
   public static class BcSupplier extends AbstractEnumBcSupplier<CxboxMyExampleController> {

       public BcSupplier() {
           super(CxboxMyExampleController.Holder);
       }

   }

}

Step5 Add assoc widget to corresponding .view.json.

{
  "name": "myexamplelist",
  "title": "MyExample Form",
  "template": "DashboardView",
  "url": "/screen/myexample/view/myexamplelist",
  "widgets": [
    {
      "widgetName": "myexampleAssoc",
      "position": 1,
      "gridWidth": 24
    },
    {
      "widgetName": "myEntityMultiAssocTreePopup",
      "position": 2,
      "gridWidth": 24
    },
    {
      "widgetName": "SecondLevelMenu",
      "position": 10,
      "gridWidth": 12
    },
    {
      "widgetName": "MyExampleList",
      "position": 20,
      "gridWidth": 12
    }
  ],
  "rolesAllowed": [
    "CXBOX_USER"
  ]
}

not applicable

not applicable

Title

Live Sample · GitHub

Title Basic

Title for widget (optional)

There are types of:

  • constant title: shows constant text.
  • constant title empty: if you want to visually connect widgets by them to be placed one under another

How does it look?

consttitle.png

empytitle.png

How to add?

Example

Step1 Add name for title to .widget.json.

{
  "name": "myEntityMultiPickAssocTreePopup",
  "title": "AssocTreePopup with title",
  "type": "AssocTreePopup",
  "bc": "myEntityMultiPick",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "input"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "hidden"
    }
  ]
}
Live Sample · GitHub

Step1 Delete parameter title to .widget.json.

{
  "name": "myEntityMultiPickAssocEmptyListPopup",
  "type": "AssocTreePopup",
  "bc": "myEntityMultiPickEmpty",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "input"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "hidden"
    }
  ]
}

Live Sample · GitHub

Title Color

Title Color allows you to specify a color for a title. It can be constant or calculated.

Constant color

Live Sample · GitHub

Constant color is a fixed color that doesn't change. It remains the same regardless of any factors in the application.

Calculated color

Live Sample · GitHub

Calculated color can be used to change a title color dynamically. It changes depending on business logic or data in the application.

Info

Title colorization is applicable to the following fields: date, dateTime, dateTimeWithSeconds, number, money, percent, time, input, text, dictionary, radio, checkbox, pickList, inlinePickList, multivalueTree, multivalueHover.

How does it look?

colorwidget.png

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 MyEntityMultiPickDTO extends DataResponseDTO {

    @SearchParameter(name = "parentId", provider = LongValueProvider.class)
    private Long parentId;

    private Boolean isLeaf;
    @SearchParameter(name = "customField")
    private String customField;
    @SearchParameter(name = "customFieldText")
    private String customFieldText;
    private String customFieldColor;
    private String customFieldColorFieldText;

    public MyEntityMultiPickDTO(MyEntityMulti entity) {
        this.id = entity.getId().toString();
        this.parentId = entity.getParentId();
        this.isLeaf = entity.getChildren().isEmpty();
        this.customField = entity.getCustomField();
        this.customFieldText = entity.getCustomFieldText();
        this.customFieldColor = "#edaa";
        this.customFieldColorFieldText = "#aeda";
    }

}

Step 2 Add "bgColorKey" : custom field for color and to .widget.json.

Add in title field with ${customField}

{
  "title": "Field Input: ${customField}, Field Text: ${customFieldText}",
  "name": "myEntityMultiPickAssocTreePopup",
  "type": "AssocTreePopup",
  "bc": "myEntityMultiPick",
  "fields": [
    {
      "title": "Custom Field Input",
      "key": "customField",
      "type": "input",
      "bgColorKey": "customFieldColor"
    },
    {
      "title": "Custom Field",
      "key": "customFieldText",
      "type": "text",
      "bgColorKey": "customFieldColorFieldText"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "hidden"
    }
  ]
}

Live Sample · GitHub

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

Add in title field with ${customField}

{
  "title": "Field Input: ${customField}, Field Text: ${customFieldText}",
  "name": "myEntityMultiPickAssocTreePopup",
  "type": "AssocTreePopup",
  "bc": "myEntityMultiPick",
  "fields": [
    {
      "title": "Custom Field Input",
      "key": "customField",
      "type": "input",
      "bgColor": "#edaa"
    },
    {
      "title": "Custom Field",
      "key": "customFieldText",
      "type": "text",
      "bgColor": "#aeda"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "hidden"
    }
  ]
}

Live Sample · GitHub

Business component

This specifies the business component (BC) to which this form belongs. A business component represents a specific part of a system that handles a particular business logic or data.

see more Business component

Show condition

Fields

Fields Configuration. The fields array defines the individual fields present within the form.

{
    "title": "Custom Field",
    "key": "customField",
    "type": "input"
}
  • "title"

Description: Field Title.

Type: String(optional).

  • "key"

Description: Name field to corresponding DataResponseDTO.

Type: String(required).

  • "type"

Description: Field types

Type: String(required).

How to add?

Example

Step 1 Download plugin download Intellij Plugin

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

Add field to .widget.json.

   {
     "title": "AssocTreePopup title",
     "name": "myEntityMultiAssocTreePopup",
     "type": "AssocTreePopup",
     "bc": "myEntityMultiAssocTreePopup",
     "fields": [
       {
         "title": "Custom Field",
         "key": "customField",
         "type": "input"
       },
       {
         "title": "id",
         "key": "id",
         "type": "text"
       },
       {
         "title": "Parent Id",
         "key": "parentId",
         "type": "hidden"
       },
       {
         "title": "Is Leaf",
         "key": "isLeaf",
         "type": "hidden"
       }
     ]
   }

Options layout

options.layout - no use in this type.

Actions

Actions show available actions as separate buttons see more Actions.

Standard Actions:

  • Create: Action to initialize the process of creating a new record
  • Delete: Remove an existing record
  • Edit: Users to update or correct information
  • Save: Action to store the data entered or modified
  • Cancel-create: Action to abort the creation of a new record, discarding any input without saving

As for assoc widget, there are several actions.

Create

Create button enables you to create a new value by clicking the Add button. This action can be performed in three different ways, feel free to choose any, depending on your logic of application:

There are three methods to create a record:

  • Inline: You can add a line directly.

Info

Pagination won't function until the page is refreshed after adding records.

  • Inline-form: You can add data using a form widget without leaving your current view.

  • With view: not applicable.

Inline

Live Sample · GitHub

With Line Addition, a new empty row is immediately added to the top of the assoc widget when the "Add" button is clicked. This is a quick way to add rows without needing to input data beforehand.

The position of the newly created row inside its node is defined by options.tree.insertPosition: start places it before the already loaded rows of the node, end places it after them. see options.tree

How does it look?

assoc_create_inline.gif

How to add?
Example

Step1 Add button create to corresponding VersionAwareResponseService.

    @Override
    public Actions<MyEntityMultiMultivalueDTO> getActions() {
        return Actions.<MyEntityMultiMultivalueDTO>builder()
                .create(crt -> crt.text("Add"))
                .save(sv -> sv.text("Save"))
                .cancelCreate(ccr -> ccr.text("Cancel").available(bc -> true))
                .delete(dlt -> dlt.text("Delete"))
                .build();
    }

Step2 Add button create to corresponding .widget.json.

The position of a newly created row inside its node is defined by options.tree.insertPosition: start places it before the already loaded rows of the node, end places it after them.

{
  "name": "myEntityMultiAssocTreePopupCreateAssocTreePopup",
  "title": "AssocTreePopup create inline",
  "type": "AssocTreePopup",
  "bc": "myEntityMultiAssocTreePopupCreate",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "input"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "hidden"
    }
  ],
  "options": {
    "actionGroups": {
      "include": [
        "create",
        "delete",
        "save"
      ]
    }
  }
}

Step3 Add fields.setEnabled to corresponding FieldMetaBuilder.

    public void buildRowDependentMeta(RowDependentFieldsMeta<MyEntityMultiMultivalueDTO> fields, InnerBcDescription bcDescription,
                                      Long id, Long parentId) {
        fields.setEnabled(MyEntityMultiMultivalueDTO_.customField);
    }
Live Sample · GitHub

Inline-form

Live Sample · GitHub

Create with widget opens an additional widget when the "Add" button is clicked. The form will appear on the same screen, allowing you to view both the assoc of entities and the form for adding a new row. After filling the information in and clicking "Save", the new row is added to the assoc.

The position of the newly created row inside its node is defined by options.tree.insertPosition: start places it before the already loaded rows of the node, end places it after them. see options.tree

How does it look?

assoc_create_with_widget.gif

How to add?
Example

Step1 Add button create to corresponding VersionAwareResponseService.

    @Override
    public Actions<MyEntityMultiMultivalueDTO> getActions() {
        return Actions.<MyEntityMultiMultivalueDTO>builder()
                .create(crt -> crt.text("Add"))
                .save(sv -> sv.text("Save"))
                .cancelCreate(ccr -> ccr.text("Cancel").available(bc -> true))
                .delete(dlt -> dlt.text("Delete"))
                .build();
    }
Step2 Add fields.setEnabled to corresponding FieldMetaBuilder.
    public void buildRowDependentMeta(RowDependentFieldsMeta<MyEntityMultiMultivalueDTO> fields, InnerBcDescription bcDescription,
                                      Long id, Long parentId) {
        fields.setEnabled(MyEntityMultiMultivalueDTO_.customField);
    }
Step3 Create widget.json with type Form that appears when you click a button
{
  "title": "",
  "name": "myEntityMultiFormForPopup",
  "type": "Form",
  "bc": "myEntityMultiAssocTreePopup",
  "fields": [
    {
      "label": "Custom Field",
      "key": "customField",
      "type": "input"
    }
  ],
  "options": {
    "layout": {
      "rows": [
        {
          "cols": [
            {
              "fieldKey": "customField",
              "span": 12
            }
          ]
        }
      ]
    },
    "actionGroups": {
      "include": [
        "save",
        "cancel-create"
      ]
    }
  }
}

Step4 Add widget.json with type Form to corresponding .view.json.

{
  "name": "myexampleinlinecreate",
  "title": "MyExample List",
  "template": "DashboardView",
  "url": "/screen/myexample/view/myexampleinlinecreate",
  "widgets": [
    {
      "widgetName": "myEntityMultiFormForPopup",
      "position": 2,
      "gridWidth": 24
    },
    {
      "widgetName": "SecondLevelMenu",
      "position": 1,
      "gridWidth": 24
    },
    {
      "widgetName": "myEntityMultiAssocTreePopup",
      "position": 2,
      "gridWidth": 24
    },
    {
      "widgetName": "ThirdLevelMenu",
      "position": 4,
      "gridWidth": 24
    },
    {
      "widgetName": "MyExampleForm",
      "position": 20,
      "gridWidth": 24
    }
  ],
  "rolesAllowed": [
    "CXBOX_USER"
  ]
}

Step5 Add button create and widget with type Form to corresponding .widget.json.

options.create: Name widget that appears when you click a button

The position of a newly created row inside its node is defined by options.tree.insertPosition: start places it before the already loaded rows of the node, end places it after them.

{
  "title": "AssocTreePopup widget action create Inline-form",
  "name": "myEntityMultiAssocTreePopup",
  "type": "AssocTreePopup",
  "bc": "myEntityMultiAssocTreePopup",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "input"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "hidden"
    }
  ],
  "options": {
    "create": {
      "widget": "myEntityMultiFormForPopup"
    },
    "edit": {
      "widget": "myEntityMultiFormForPopup"
    },
    "actionGroups": {
      "include": [
        "create",
        "delete"
      ]
    }
  }
}

Live Sample · GitHub

With view

not applicable

Delete

Live Sample · GitHub

Delete remove an existing record.

Tips

Please note that the row you are attempting to delete may be referenced by another part of the system or a parent entity. To ensure clarity, you should handle this exception and provide a explanation to the user.

How does it look?

actiondelete.gif

How to add?
Example

Step1 Add action delete to corresponding VersionAwareResponseService.

By default, the access button is available when a record exist.

    @Override
    public Actions<MyExampleDTO> getActions() {
        return Actions.<MyExampleDTO>builder()
                .save(sv -> sv.text("Save"))
                .create(crt -> crt)
                .delete(dlt -> dlt)
                .build();
    }

Step2 Add button or group button to corresponding .widget.json.

{
  "title": "AssocTreePopup widget action delete",
  "name": "myEntityMultiAssocDeleteListPopup",
  "type": "AssocTreePopup",
  "bc": "myEntityMultiAssocDeleteListPopup",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "input"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "hidden"
    }
  ],
  "options": {
    "actionGroups": {
      "include": [
        "create",
        "delete"
      ]
    }
  }
}
Live Sample · GitHub

Edit

Edit enables you to change the field value. Just like with Create button, there are three ways of implementing this Action.

There are three methods to create a record:

  • Inline edit: You can edit a line directly.

  • Inline-form: You can edit data using a form widget without leaving your current view.

  • With view: not applicable

Inline edit

Live Sample · GitHub

Edit Inline implies inline-edit. Click twice on the value you want to change.

How does it look?

assoc_edit_basic.gif

How to add?
Example

Step1 Add fields.setEnabled to corresponding FieldMetaBuilder.

    public void buildRowDependentMeta(RowDependentFieldsMeta<MyEntityMultiMultivalueDTO> fields, InnerBcDescription bcDescription,
                                      Long id, Long parentId) {
        fields.setEnabled(MyEntityMultiMultivalueDTO_.customField);
    }

Live Sample · GitHub

Inline-form

Live Sample · GitHub

Edit with widget opens an additional widget when clicking the edit icon in the row.

How does it look?

assoc_edit_with_widget.gif

How to add?
Example

Step1 Add action save to corresponding VersionAwareResponseService: the form saves the record with it.

    @Override
    public Actions<MyEntityMultiMultivalueDTO> getActions() {
        return Actions.<MyEntityMultiMultivalueDTO>builder()
                .create(crt -> crt.text("Add"))
                .save(sv -> sv.text("Save"))
                .cancelCreate(ccr -> ccr.text("Cancel").available(bc -> true))
                .delete(dlt -> dlt.text("Delete"))
                .build();
    }

Step2 Add fields.setEnabled to corresponding FieldMetaBuilder.

    public void buildRowDependentMeta(RowDependentFieldsMeta<MyEntityMultiMultivalueDTO> fields, InnerBcDescription bcDescription,
                                      Long id, Long parentId) {
        fields.setEnabled(MyEntityMultiMultivalueDTO_.customField);
    }

Step2 Create widget.json with type Form that appears when you click a button

{
  "title": "",
  "name": "myEntityMultiFormForPopup",
  "type": "Form",
  "bc": "myEntityMultiAssocTreePopupEditInlineForm",
  "fields": [
    {
      "label": "Custom Field",
      "key": "customField",
      "type": "input"
    }
  ],
  "options": {
    "layout": {
      "rows": [
        {
          "cols": [
            {
              "fieldKey": "customField",
              "span": 12
            }
          ]
        }
      ]
    },
    "actionGroups": {
      "include": [
        "save",
        "cancel-create"
      ]
    }
  }
}

Step4 Add widget.json with type Form to corresponding .view.json.

{
  "name": "myexampleeditinlineform",
  "title": "MyExample List",
  "template": "DashboardView",
  "url": "/screen/myexample/view/myexampleeditinlineform",
  "widgets": [
    {
      "widgetName": "SecondLevelMenu",
      "position": 1,
      "gridWidth": 24
    },
    {
      "widgetName": "myEntityMultiAssocTreePopupEditInlineForm",
      "position": 2,
      "gridWidth": 24
    },
    {
      "widgetName": "myEntityMultiFormForPopup",
      "position": 3,
      "gridWidth": 24
    },
    {
      "widgetName": "ThirdLevelMenu",
      "position": 4,
      "gridWidth": 24
    },
    {
      "widgetName": "MyExampleFormEdit",
      "position": 20,
      "gridWidth": 24
    }
  ],
  "rolesAllowed": [
    "CXBOX_USER"
  ]
}

Step5 Add widget with type Form to options.edit.widget of corresponding .widget.json: the edit icon appears in each row.

options.edit: Name widget that appears when you click a button

{
  "title": "AssocTreePopup widget action create Inline-form",
  "name": "myEntityMultiAssocTreePopupEditInlineForm",
  "type": "AssocTreePopup",
  "bc": "myEntityMultiAssocTreePopupEditInlineForm",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "input"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "hidden"
    }
  ],
  "options": {
    "edit": {
      "widget": "myEntityMultiFormForPopup"
    },
    "actionGroups": {
      "include": []
    }
  }
}

Live Sample · GitHub

With view

not applicable

Additional properties

Node refresh

After an action (save, delete, cancel-create) the popup refreshes only the node the record belongs to, in the same way as a Tree widget:

  • the row is updated from the response of the action or, if the response does not contain it, re-read with the request ?id.equals=<id>;
  • the row-meta of the row is requested again;
  • if the record is not returned any more, the row is removed from the tree;
  • the node is collapsed and its already loaded child records are forgotten, so they are loaded again on the next expand.

Info

Sibling records and parent records are not refreshed automatically. PostAction.refreshBC is not supported for a tree in this release: the root page is loaded again, but the expanded nodes and their already loaded child records stay as they are.

Customization of displayed columns

not applicable

Filtration

Basic

see more Fields

Filtration of the popup is a standard request. The rows that match the filter are returned by the backend without any hierarchy, and the popup decides how to display them, see Search modes.

The panel above the tree shows:

  • Clear N filter(s) — the number of applied filters and the possibility to reset them;
  • Shown N — how many found records are currently displayed;
  • More M — how many found records are not displayed yet, where M is the number of found records minus the number of shown records.

Info

With the pagination mode nextAndPreviousWithCount the /count request is not performed after filtration. Instead of the number of found records an i icon is displayed with the tooltip "Load more and show count".

FullTextSearch

FullTextSearch - when the user types in the full text search input area, then widget filters the rows that match the search query. see FullTextSearch

The result of a full text search is displayed in the same way as the result of filtration, see Search modes.

Personal filter group

not applicable

Filter group

not applicable

Search modes

Live Sample · GitHub

The way the result of filtration and full text search is displayed is defined by options.tree.searchModes.

There are two modes:

  • collapse — search results and tree. The tree displays the found records together with the records that are required for navigation: the navigation path above every found record is restored onFilterApplyNestLevel levels up. The user can navigate through the tree, expand and collapse branches, see the neighbouring records and load additional records.
  • hide — search results only. The tree displays only the found records, everything else is hidden. The user works with the search result only and cannot navigate through the other records of the tree.

Both modes can be available at the same time. The first item of the searchModes array is the mode that is active when the filter is applied; the user switches between the available modes in the gear menu of the popup.

Restoring the path upwards

In collapse mode the records whose parents have not been loaded yet are placed under a pseudo node. The >... button loads the missing parents with the request ?id.equals=<parentId> and moves the records into the hierarchy. One click restores up to 2 levels of nesting, so for a deep hierarchy the button has to be pressed several times.

How does it look?

search_collapse.png

search_hide.png

search_modes_menu.png

How to add?
Example

Step1 Add options.tree.searchModes to corresponding .widget.json.

The first item of the array is the mode that is active when the filter is applied.

"tree": {
  "searchModes": ["collapse", "hide"],
  "onFilterApplyNestLevel": 0
}
{
  "name": "myexampleAssocTreePopup",
  "title": "Select Department (AssocTreePopup)",
  "type": "AssocTreePopup",
  "bc": "myexamplePick",
  "fields": [
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Departments",
      "key": "department",
      "type": "input"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "checkbox"
    },
    {
      "title": "Description",
      "key": "description",
      "type": "input"
    },
    {
      "title": "Code",
      "key": "code",
      "type": "input"
    },
    {
      "title": "Mnemonic",
      "key": "mnemonic",
      "type": "input"
    }
  ],
  "options": {
    "fullTextSearch": {
      "enabled": true,
      "placeholder": "Find by department"
    },
    "tree": {
      "searchModes": ["collapse", "hide"],
      "selection": "node"
    }
  }
}

Live Sample · GitHub

Pagination is the process of dividing content into separate, discrete pages, making it easier to navigate and consume large amounts of information. see Pagination

The popup has no navigation arrows. Instead of the "next" arrow the last row of a node is the More button, which loads the next page of the node and appends it to the already loaded records. The limit selector (availableLimitsList) is moved to the gear menu of the popup.

The More button follows the same algorithm as the "next" arrow of the three pagination modes:

Pagination mode When More is displayed Counter
nextAndPreviousWithHasNext hasNext returned by the backend is true not displayed
nextAndPreviousWithCount count is greater than the number of already loaded records how many records are left to load
nextAndPreviousSmart the backend returned more records than the limit of the node not displayed

see more Pagination modes

How does it look?

lazyload.png

How to add?
Example

The list of available limits is displayed in the gear menu of the popup.

"pagination": {
  "availableLimitsList": [1, 2, 3]
}
{
  "name": "myEntityMultiPickAssocTreePopup",
  "title": "AssocTreePopup title",
  "type": "AssocTreePopup",
  "bc": "myEntityMultiPick",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "input"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    }
  ],
  "options": {
    "pagination": {
      "availableLimitsList": [
        1,
        2,
        3
      ]
    }
  }
}

GitHub

The default number of the records requested for a node is defined by the page limit, see Page limit.

{
  "name": "myEntityAssocPickAssocTreePopup",
  "title": "AssocTreePopup property Default limit page",
  "type": "AssocTreePopup",
  "bc": "myEntityAssocPick",
  "fields": [
    {
      "title": "Custom Field",
      "key": "customField",
      "type": "input"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    }
  ]
}

GitHub

Export to Excel

not applicable

Multi-upload files

not applicable

Sorting

Sorting allows the user to sort the records by a column. see Sorting

Sorting of the popup works inside a node: the records are sorted among the children of the same parent, the hierarchy itself is not changed. In all other respects sorting is standard.

Selection modes

Live Sample · GitHub

Checkboxes are available both on the nodes and on the leaves of the tree. What exactly the user is allowed to check is defined by options.tree.selection:

  • node — only the nodes can be checked.
  • leaf — only the leaves can be checked.
  • nodeAndLeaf — both the nodes and the leaves can be checked.

Info

The checkbox of the More row is always disabled: this row is a pagination control, not a record.

Rule 1. What the user selected explicitly is marked with a bright checkmark, and exactly that is sent to the backend. Everything that is selected indirectly, as a consequence of an explicit selection, is marked with a dimmed checkmark or a dimmed square.

The following cases are possible:

  • The user picks the leaves manually. The identifiers of the picked leaves are sent to the backend. The parent group is marked with a dimmed square, because the group itself is not selected.

  • The user picks a group. The identifier of the group is sent to the backend. All the children of the group, including the More row, become dimmed: they are selected as a part of the group and cannot be selected separately.

  • The user unchecks a child of a selected group. The selection switches to the manual mode: the group is no longer sent to the backend, instead the identifiers of the remaining children are sent.

Info

Limitation: the selection "the group minus N records" cannot be expressed. Such a selection is always transformed into the explicit list of the remaining records.

  • The user checks all the children of a group manually. This is not the same as picking the group itself. The two selections are visually distinguishable, and they behave differently: when new records of that group appear, they are not selected in the manual case, while they are a part of the selection when the group itself is picked.
Confirmation for a node that is not fully loaded

A node may have records that are not loaded yet, hidden behind the More row. options.tree.confirms defines which actions on such a node are confirmed:

Value Description
paginationUnselect The user unchecks a child of a selected node. After the confirmation the selection switches to the manual mode: only the loaded records stay selected, the hidden ones are lost.
paginationSelect The user checks the node itself. After the confirmation the whole node is selected, including the records hidden behind More.

The default value is ["paginationUnselect"], an empty array disables the confirmations. see more options.tree

How does it look?

selection_group.png

selection_manual.png

selection_confirm.png

How to add?
Example

Add in options parameter tree to corresponding .widget.json.

"tree": {
  "selection": "node",
  "confirms": ["paginationUnselect"]
}
{
  "name": "myexampleAssocTreePopup",
  "title": "Select Department (AssocTreePopup)",
  "type": "AssocTreePopup",
  "bc": "myexamplePick",
  "fields": [
    {
      "title": "Parent Id",
      "key": "parentId",
      "type": "hidden"
    },
    {
      "title": "id",
      "key": "id",
      "type": "text"
    },
    {
      "title": "Departments",
      "key": "department",
      "type": "input"
    },
    {
      "title": "Is Leaf",
      "key": "isLeaf",
      "type": "checkbox"
    },
    {
      "title": "Description",
      "key": "description",
      "type": "input"
    },
    {
      "title": "Code",
      "key": "code",
      "type": "input"
    },
    {
      "title": "Mnemonic",
      "key": "mnemonic",
      "type": "input"
    }
  ],
  "options": {
    "fullTextSearch": {
      "enabled": true,
      "placeholder": "Find by department"
    },
    "tree": {
      "searchModes": ["collapse", "hide"],
      "selection": "node"
    }
  }
}

Live Sample · GitHub

Info

The button usage and the multivalueTree field usage differ in the way the already selected values are shown.

On a field with type multivalueTree the popup shows the already selected values as tags at the top, so the user continues the previous selection.

The popup opened by a button is not directly tied to a field, so it always opens from scratch, without the tags of the already selected values. Add filtering to the opened association in order to exclude the records that have already been added.