Composito component inject component

Hi everyone, I created composite component how to inject the components into the controller? I need to attach a onClick and onChange listener. Thanks

Hello!

Could you clarify which Jmix version you are using? Also, do you mean injecting a component into the View controller, or injecting inner components of a composite component into the component itself?

Jmix version: 2.6.0
Jmix Studio Plugin Version: 2.6.2-252
IntelliJ version: IntelliJ IDEA 2025.2 (Community Edition)
I want to inject the component into the view controller. The composite component is a JmixSelect + Button. Then i want to add a clicklistener on that button like i do with the other components like this @Subscribe(“statusField”)
// public void onStatusFieldComponentValueChange(final AbstractField.ComponentValueChangeEvent<JmixSelect<?>, ?> event) {
… …
}

class is this package it.evisionsrl.cdaplus.components.selectHelper;

import com.vaadin.flow.component.AbstractField;
import com.vaadin.flow.component.AbstractSinglePropertyField;
import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexLayout;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import io.jmix.flowui.UiComponents;
import io.jmix.flowui.component.select.JmixSelect;
import io.jmix.flowui.data.value.ContainerValueSource;
import io.jmix.flowui.kit.component.ComponentUtils;
import io.jmix.flowui.kit.component.button.JmixButton;
import io.jmix.flowui.model.InstanceContainer;
import it.evisionsrl.cdaplus.config.ConfigurationSelect;
import it.evisionsrl.cdaplus.service.HelperService;
import lombok.Getter;
import lombok.Setter;

public class SelectWithHelper extends Composite {
//public class SelectWithHelper extends Composite

{
//public class SelectWithHelper extends Composite {

@Getter
private JmixSelect select;
@Getter
private JmixButton button;
@org.springframework.beans.factory.annotation.Autowired
protected UiComponents uiComponents;
@Setter
private String itemKey;
private String idField;
private ConfigurationSelect configurationSelect;

HelperService helperService;

public SelectWithHelper(UiComponents uiComponents, ConfigurationSelect configurationSelect, HelperService helperService) {
    this.uiComponents = uiComponents;
    this.configurationSelect=configurationSelect;
    this.select = uiComponents.create(JmixSelect.class);
    this.helperService = helperService;
    this.button = uiComponents.create(JmixButton.class);

    System.out.println("INIT");
    System.out.println(this.button);
}

@Override
protected HorizontalLayout initContent() {
    HorizontalLayout content = super.initContent();
    content.add(select,  button );
    content.getStyle().set("display","flex");
    content.getStyle().set("gap","0");

// content.getStyle().set(“background-color”,“hsla(214, 57%, 24%, 0.1)”);
// content.getStyle().set(“background-color”,“var(–lumo-contrast-10pct”);

    select.addClassName("select-helper");
    Icon icon = VaadinIcon.QUESTION_CIRCLE.create();
    icon.getStyle().set("color","var(--lumo-secondary-text-color)");

// icon.setColor(" color: var(–lumo-secondary-text-color);");

    button.setIcon(icon);
    button.getStyle().set("border-radius","0");
    button.getStyle().set("background-color","var(--lumo-contrast-10pct)");
    button.getStyle().set("margin-top","4px");
    button.getStyle().set("margin-bottom","4px");
    button.getStyle().set("fill","currentColor");

// ComponentUtils.setItemsMap(select, configurationSelect.getStatusMaps());
// System.out.println("ITEMS: " + itemKey);
// ComponentUtils.setItemsMap(select, configurationSelect.getFieldsWithSelect().get(itemKey));

// this.button = this.helperService.createHelperButton(getId().get());

    return content;
}


public void setDataContainer(InstanceContainer<Object> instanceContainer, String idField) {
    select.setValueSource(new ContainerValueSource<>(instanceContainer, itemKey));

// System.out.println(itemKey);
// System.out.println(configurationSelect.getFieldsWithSelect().get(itemKey));
// System.out.println("REG: " + ComponentUtils.(select, configurationSelect.getFlPcMaps()));

    ComponentUtils.setItemsMap(select, configurationSelect.getFieldsWithSelect().get(itemKey));
    this.button = this.helperService.createSingleClickListener(this.button,idField);

}

}

Let’s imagine we have a HelperSelect component.

HelperSelect.class
import com.vaadin.flow.component.*;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.select.Select;
import com.vaadin.flow.shared.Registration;

import java.util.EventObject;
import java.util.function.Consumer;

public class HelperSelect extends Composite<HorizontalLayout> implements HasSize {

    private Select<String> select;
    private Button helperButton;

    @Override
    protected HorizontalLayout initContent() {
        HorizontalLayout layout = new HorizontalLayout();

        select = createSelect();
        helperButton = createButton();

        layout.add(select, helperButton);

        return layout;
    }

    public Registration addHelperButtonClickListener(Consumer<HelperButtonClickEvent> listener) {
        return helperButton.addClickListener(event -> {
            listener.accept(new HelperButtonClickEvent(helperButton, this, event.isFromClient()));
        });
    }

    public Registration addValueChangeListener(HasValue.ValueChangeListener<AbstractField.ComponentValueChangeEvent<Select<String>, String>> listener) {
        return select.addValueChangeListener(listener);
    }

    private Select<String> createSelect() {
        Select<String> select = new Select<>();
        select.setItems("One", "Two", "Three");
        return select;
    }

    private Button createButton() {
        Button helperButton = new Button();
        helperButton.setIcon(VaadinIcon.QUESTION_CIRCLE.create());
        return helperButton;
    }

    public static class HelperButtonClickEvent extends EventObject {

        protected HelperSelect helperSelect;

        protected boolean fromClient;

        public HelperButtonClickEvent(Button source, HelperSelect helperSelect, boolean fromClient) {
            super(source);
            this.helperSelect = helperSelect;
            this.fromClient = fromClient;
        }

        @Override
        public Button getSource() {
            return (Button) source;
        }

        public HelperSelect getHelperSelect() {
            return helperSelect;
        }

        public boolean isFromClient() {
            return fromClient;
        }
    }
}

To enable generation handlers for custom component and injecting it to a controller from a descriptor, follow the following steps:

  1. Create XSD schema to enable working with component in View descriptors:

    ui.xsd
    <xs:schema targetNamespace="http://myschema.io/schema/demo/ui"
               xmlns:xs="http://www.w3.org/2001/XMLSchema"
               xmlns:layout="http://jmix.io/schema/flowui/layout"
               xmlns="http://myschema.io/schema/demo/ui"
               elementFormDefault="qualified">
    
        <xs:element name="helperSelect">
            <xs:complexType>
                <xs:complexContent>
                    <xs:extension base="layout:baseComponent">
                        <xs:attributeGroup ref="layout:hasSize"/>
                    </xs:extension>
                </xs:complexContent>
            </xs:complexType>
        </xs:element>
    </xs:schema>
    
  2. Create loader class to load component from View descriptor.

    HelperSelectLoader.class
    public class HelperSelectLoader extends AbstractComponentLoader<HelperSelect> {
    
        @Override
        protected HelperSelect createComponent() {
            return factory.create(HelperSelect.class);
        }
    
        @Override
        public void loadComponent() {
            componentLoaderSupport.loadSizeAttributes(resultComponent, element);
        }
    }
    
  3. Register component with loader in Jmix components factory:

    @Bean
    public ComponentRegistration helperSelect() {
        return ComponentRegistrationBuilder.create(HelperSelect.class)
                .withComponentLoader("helperSelect", HelperSelectLoader.class)
                .build();
    }
    
  4. In order for the component to be recognized by Studio, create a Studio description for the component.

    StudioMyDemoComponents.class
    import com.company.demo.component.HelperSelect;
    import io.jmix.flowui.kit.meta.*;
    
    @StudioUiKit
    public interface StudioMyDemoComponents {
    
        @StudioComponent(
                name = "HelperSelect",
                xmlElement = "helperSelect",
                xmlnsAlias = "hs",
                xmlns = "http://myschema.io/schema/demo/ui",
                category = "Components",
                classFqn = "com.company.demo.component.HelperSelect",
                properties = {
                        @StudioProperty(xmlAttribute = "alignSelf", category =StudioProperty.Category.POSITION, type = StudioPropertyType.ENUMERATION,
                                classFqn = "com.vaadin.flow.component.orderedlayout.FlexComponent$Alignment",
                                defaultValue = "AUTO",
                                options = {"START", "END", "CENTER", "STRETCH", "BASELINE", "AUTO"}),
                        @StudioProperty(xmlAttribute = "css", category = StudioProperty.Category.LOOK_AND_FEEL, type = StudioPropertyType.STRING),
                        @StudioProperty(xmlAttribute = "height", category = StudioProperty.Category.SIZE, type = StudioPropertyType.SIZE, options = {"AUTO", "100%"}),
                        @StudioProperty(xmlAttribute = "id", category = StudioProperty.Category.GENERAL, type = StudioPropertyType.COMPONENT_ID),
                        @StudioProperty(xmlAttribute = "maxHeight", category = StudioProperty.Category.SIZE, type = StudioPropertyType.SIZE, options = {"AUTO", "100%"}),
                        @StudioProperty(xmlAttribute = "maxWidth", category = StudioProperty.Category.SIZE, type = StudioPropertyType.SIZE, options = {"AUTO", "100%"}),
                        @StudioProperty(xmlAttribute = "minHeight", category = StudioProperty.Category.SIZE, type = StudioPropertyType.SIZE, options = {"AUTO", "100%"}),
                        @StudioProperty(xmlAttribute = "minWidth", category = StudioProperty.Category.SIZE, type = StudioPropertyType.SIZE, options = {"AUTO", "100%"}),
                        @StudioProperty(xmlAttribute = "visible", category = StudioProperty.Category.GENERAL, type = StudioPropertyType.BOOLEAN,
                             defaultValue = "true"),
                        @StudioProperty(xmlAttribute = "width", category = StudioProperty.Category.SIZE, type = StudioPropertyType.SIZE, options = {"AUTO", "100%"}),
                }
        )
        HelperSelect helperSelect();
    }
    
  5. Refresh gradle in your project.

Now the component can be injected to the controller and its handlers can be generated:

@Subscribe("helperSelect")
public void onHelperSelectComponentValueChange(final AbstractField.ComponentValueChangeEvent<HelperSelect, ?> event) {
    notifications.show("HelperSelect value changed: " + event.getValue());
}
@Subscribe("helperSelect")
public void onHelperSelectHelperButtonClick(final HelperSelect.HelperButtonClickEvent event) {
    notifications.show("Button is clicked");
}

The full example: demo-2-6-x.zip (90.5 KB)

1 Like