Entity Instance issues using UIComponents

Hello,

Hopefully this is easy and I’m just forgetting to do something, but I haven’t found examples in the documentation or Jmix UI Samples app.

This issue I’m running into is that I can’t seem to get any result other than detached entity objects when implementing the UIComponents bean to create a RadioButtonGroup or CheckboxGroup component.

I have an instance name set for my SurveyAnswerOption class, and I’ve tried expanding the fetchPlan to use streams from a collection container, as well as using a service instead of a collection container to pull a list of entities directly. In both of these attempts, I’m setting the list of options using RadioButtonGroup.setItems() or CheckboxGroup.setItems(). Anything I can think to try produces a detached visual representation of the entities to select from.

This Jmix 2.0 entity detail view builds components dynamically based off of a SurveyQuestion class.

Commented out lines are examples of what I have also tried:

View controller:

    private void buildRadioButtonGroup(SurveyQuestion surveyQuestion) {
        RadioButtonGroup radioButtonGroup = uiComponents.create(RadioButtonGroup.class);
        radioButtonGroup.setId(String.valueOf(surveyQuestion.getId()));
        radioButtonGroup.setLabel(surveyQuestion.getQuestionText());
        radioButtonGroup.setRequired(true);
//        radioButtonGroup.setItems(getAnswerOptions(surveyQuestion));
        radioButtonGroup.setItems(surveyAnswerOptionsDc.getItems());

        form.add(radioButtonGroup);
    }

    private List<SurveyAnswerOption> getAnswerOptions(SurveyQuestion surveyQuestion) {
        return surveyResponseService.getAnswerOptions(surveyQuestion);
//        return surveyAnswerOptionsDc.getItems()
//            .stream().filter(answerOption -> surveyQuestion.equals(answerOption.getSurveyQuestion()))
//            .sorted(Comparator.comparing(SurveyAnswerOption::getDisplayOrder))
//            .collect(Collectors.toList());
    }

Service class:

@Service
@Transactional
public class SurveyResponseService {
    @Autowired
    private DataManager dataManager;
    private static final String GET_ANSWER_OPTIONS = "select e from SurveyAnswerOption e where e.surveyQuestion = :surveyQuestion order by e.displayOrder";

    public List<SurveyAnswerOption> getAnswerOptions(SurveyQuestion surveyQuestion) {
        return dataManager.load(SurveyAnswerOption.class)
                .query(GET_ANSWER_OPTIONS)
                .parameter("surveyQuestion", surveyQuestion)
                .fetchPlan("_base")
                .list();
    }

}

View descriptor fetch plan:

        <collection id="surveyAnswerOptionsDc" class="com.company.survey.entity.SurveyAnswerOption">
            <fetchPlan extends="_base"/>
            <loader id="surveyAnswerOptionsDl">
                <query>
                    <![CDATA[select e from SurveyAnswerOption e]]>
                </query>
            </loader>
        </collection>

Entity class:

package com.company.survey.entity;

import io.jmix.core.DeletePolicy;
import io.jmix.core.annotation.DeletedBy;
import io.jmix.core.annotation.DeletedDate;
import io.jmix.core.entity.annotation.JmixGeneratedValue;
import io.jmix.core.entity.annotation.OnDeleteInverse;
import io.jmix.core.metamodel.annotation.InstanceName;
import io.jmix.core.metamodel.annotation.JmixEntity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;

import java.time.OffsetDateTime;
import java.util.UUID;

@JmixEntity
@Table(name = "SURVEY_ANSWER_OPTION", indexes = {
        @Index(name = "IDX_SURVEY_ANSWER_OPTION_SURVEY_QUESTION", columnList = "SURVEY_QUESTION_ID")
})
@Entity
public class SurveyAnswerOption {
    @InstanceName
    @Column(name = "ANSWER_TEXT", nullable = false)
    @NotNull
    private String answerText;

    @Column(name = "DISPLAY_ORDER", nullable = false)
    @NotNull
    private Integer displayOrder;

    @JmixGeneratedValue
    @Column(name = "ID", nullable = false)
    @Id
    private Integer id;

    @JmixGeneratedValue
    @Column(name = "UUID")
    private UUID uuid;

    @Column(name = "VERSION", nullable = false)
    @Version
    private Integer version;

    @CreatedBy
    @Column(name = "CREATED_BY")
    private String createdBy;

    @CreatedDate
    @Column(name = "CREATED_DATE")
    private OffsetDateTime createdDate;

    @LastModifiedBy
    @Column(name = "LAST_MODIFIED_BY")
    private String lastModifiedBy;

    @LastModifiedDate
    @Column(name = "LAST_MODIFIED_DATE")
    private OffsetDateTime lastModifiedDate;

    @DeletedBy
    @Column(name = "DELETED_BY")
    private String deletedBy;

    @DeletedDate
    @Column(name = "DELETED_DATE")
    private OffsetDateTime deletedDate;

    @OnDeleteInverse(DeletePolicy.CASCADE)
    @JoinColumn(name = "SURVEY_QUESTION_ID", nullable = false)
    @ManyToOne(fetch = FetchType.LAZY, optional = false)
    private SurveyQuestion surveyQuestion;

    public SurveyQuestion getSurveyQuestion() {
        return surveyQuestion;
    }

    public void setSurveyQuestion(SurveyQuestion surveyQuestion) {
        this.surveyQuestion = surveyQuestion;
    }

    public Integer getDisplayOrder() {
        return displayOrder;
    }

    public void setDisplayOrder(Integer displayOrder) {
        this.displayOrder = displayOrder;
    }

    public String getAnswerText() {
        return answerText;
    }

    public void setAnswerText(String answerText) {
        this.answerText = answerText;
    }

    public OffsetDateTime getDeletedDate() {
        return deletedDate;
    }

    public void setDeletedDate(OffsetDateTime deletedDate) {
        this.deletedDate = deletedDate;
    }

    public String getDeletedBy() {
        return deletedBy;
    }

    public void setDeletedBy(String deletedBy) {
        this.deletedBy = deletedBy;
    }

    public OffsetDateTime getLastModifiedDate() {
        return lastModifiedDate;
    }

    public void setLastModifiedDate(OffsetDateTime lastModifiedDate) {
        this.lastModifiedDate = lastModifiedDate;
    }

    public String getLastModifiedBy() {
        return lastModifiedBy;
    }

    public void setLastModifiedBy(String lastModifiedBy) {
        this.lastModifiedBy = lastModifiedBy;
    }

    public OffsetDateTime getCreatedDate() {
        return createdDate;
    }

    public void setCreatedDate(OffsetDateTime createdDate) {
        this.createdDate = createdDate;
    }

    public String getCreatedBy() {
        return createdBy;
    }

    public void setCreatedBy(String createdBy) {
        this.createdBy = createdBy;
    }

    public Integer getVersion() {
        return version;
    }

    public void setVersion(Integer version) {
        this.version = version;
    }

    public UUID getUuid() {
        return uuid;
    }

    public void setUuid(UUID uuid) {
        this.uuid = uuid;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }
}

Visual result:
image

What am I missing/forgetting?

Thanks in advance,
Adam

Hi, Adam.

It looks like you forgot to set some presentation rule for RadioButtonGroup.
Because of this, the standard JPA-entity presentation is used.

The simplest way to do this is to use “JmixRadioButtonGroup” which automatically sets the ItemLabelGenerator for your component.

Another way to do this is to define the item label generator yourself, comboBox example from docs:
https://docs.jmix.io/jmix/flow-ui/vc/components/comboBox.html#itemLabelGenerator

Also, you can use the setItems method from the ComponentUtils class.
Here is a usage example from the sampler: link

Regards,
Dmitriy

In fact, JmixRadioButtonGroup should have been created automatically when using uiComponents bean.
I created a bug-issue: RadioButtonGroup not registered with UiComponents · Issue #2139 · jmix-framework/jmix · GitHub

Hello Dmitriy,

Well, that’s making a lot of sense and explaining why I was getting a direct Vaadin object after instantiating. Thank you for the information and links.

Thank you,
Adam