ListView with ItemDetails

Hi everyone, I am using this https://demo.jmix.io/ui-samples/sample/data-grid-item-details?tab=Description demo code to implement a listview with itemdetails, i have a problem when in the details i try to show a field which has a o2m relationship. I got this error IllegalArgumentException: Can’t create component for the ‘itemsB’ with given meta class ‘A’ Thanks

Hello,

Yes, this will happen if you try to add list to form. You copied standard snippet that not intended to used with o2m fields.

Solution

Fastest and simplest way to solve you problem - put own logics to generate grid on you own:

public FormLayout createCustomerDetailsRenderer() {
        FormLayout formLayout = uiComponents.create(FormLayout.class);
        formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2));

        generateComponent(formLayout, metaClass.getProperty("username"));
        generateComponent(formLayout, metaClass.getProperty("firstName"));
        generateComponent(formLayout, metaClass.getProperty("lastName"));
        generateComponent(formLayout, metaClass.getProperty("active"));
        generateComponent(formLayout, metaClass.getProperty("email"));

        Grid<Pet> usersDataGrid = new Grid<>(Pet.class);
        usersDataGrid.setItems(new ContainerDataGridItems<>(dataComponents.createCollectionContainer(Pet.class, instanceContainer, "pets")));
        formLayout.add(usersDataGrid);

        return formLayout;
    }

So, this will work fine:

image

But, as i mentioned FormLayout IS NOT designed to handle datagrids in form xd

Basically, just wrap all into vertical (or horisontal) layout:

image

@UIScope
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
@Component("uisamples_CustomerDetailsGenerator")
public class UsersDetailsGenerator {
    protected final UiComponents uiComponents;
    protected final UiComponentsGenerator componentsGenerator;
    protected final DataComponents dataComponents;
    protected final MessageTools messageTools;
    protected final MetaClass metaClass;
    protected boolean readOnlyMode;
    private final InstanceContainer<User> instanceContainer;
    private final FormLayout formLayout;

    public UsersDetailsGenerator(UiComponents uiComponents,
                                    Metadata metadata,
                                    UiComponentsGenerator componentsGenerator,
                                    DataComponents dataComponents,
                                    MessageTools messageTools) {
        this.uiComponents = uiComponents;
        this.componentsGenerator = componentsGenerator;
        this.dataComponents = dataComponents;
        this.messageTools = messageTools;
        this.metaClass = metadata.getClass(User.class);

        instanceContainer = dataComponents.createInstanceContainer(User.class);
        formLayout = uiComponents.create(FormLayout.class);
    }

    public VerticalLayout createCustomerDetailsRenderer() {
        VerticalLayout root = new VerticalLayout();


        formLayout.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 2));

        generateComponent(formLayout, metaClass.getProperty("username"));
        generateComponent(formLayout, metaClass.getProperty("firstName"));
        generateComponent(formLayout, metaClass.getProperty("lastName"));
        generateComponent(formLayout, metaClass.getProperty("active"));
        generateComponent(formLayout, metaClass.getProperty("email"));

        Grid<Pet> usersDataGrid = new Grid<>(Pet.class);
        usersDataGrid.setItems(new ContainerDataGridItems<>(dataComponents.createCollectionContainer(Pet.class, instanceContainer, "pets")));

        root.add(formLayout);
        root.add(usersDataGrid);

        return root;
    }

    protected void generateComponent(FormLayout formLayout, MetaProperty metaProperty) {
        ComponentGenerationContext componentGenerationContext =
                new ComponentGenerationContext(metaClass, metaProperty.getName());

        com.vaadin.flow.component.Component generatedComponent =
                componentsGenerator.generate(componentGenerationContext);
        generatedComponent.setId(metaProperty.getName() + "Field");

        formLayout.add(generatedComponent);
    }

    public void setUser(VerticalLayout layout, User customer) {
        instanceContainer.setItem(customer);

        formLayout.getChildren()
                .forEach(component -> setValueToComponent(component, instanceContainer));
    }

    protected void setValueToComponent(com.vaadin.flow.component.Component component,
                                       InstanceContainer<User> instanceContainer) {
        if (component.getId().isEmpty()) {
            return;
        }

        String id = component.getId().get();
        String propertyName = id.replace("Field", "");

        if (component instanceof SupportsValueSource<?> valueSourceComponent) {
            valueSourceComponent.setValueSource(new ContainerValueSource<>(instanceContainer, propertyName));
        }

        if (component instanceof HasLabel hasLabelComponent) {
            MetaClass metaClass = instanceContainer.getEntityMetaClass();
            hasLabelComponent.setLabel(messageTools.getPropertyCaption(metaClass, propertyName));
        }

        if (component instanceof HasValue<?, ?> hasValueComponent) {
            hasValueComponent.setReadOnly(readOnlyMode);
        }
    }

    public void setReadOnlyMode(boolean readOnlyMode) {
        this.readOnlyMode = readOnlyMode;
    }
}

    protected ComponentRenderer<VerticalLayout, User> createCustomerDetailsRenderer() {
        return new ComponentRenderer<>(detailsGenerator::createCustomerDetailsRenderer, detailsGenerator::setUser);
    }

Refactor

For this case i recomment not lay on ComponentGenerationContext and copy-pasting elements, just code inline createCustomerDetailsRenderer, as you can see, this is just vertical layout with form (that you also can fill via data container & grid.

Sample

datagrid-detail-with-list-field.zip (1.1 MB)

Best regards,
Dmitry