ComboBox - Filter contents based on another ComboBox value

I have a form that I am working on and I would like the values of one ComboBox to filter based on the value selected in another ComboBox. In my example, I have two ComboBoxes, questionSectionBox and questionSubsectionBox. I would like the values visible in questionSubsectionBox to be only ones that have a parent ID of the selected value in questionSectionBox. I have already implemented a property in the questionSubsectionBox that ties each Subsection to a “parentSection”. This sounds simple to me, but I’m struggling to get it implemented.

Any ideas? I’m thinking if a use a ValueChangeListener I should be able to make this work, but I also need it to filter on initial load when selecting the existing items.

Here are my two ComboBoxes:


		questionSectionBox = new ComboBox();
		questionSectionBox.setFilteringMode(Filtering.FILTERINGMODE_CONTAINS);

		List<Section> sections = Section.values();

		for (Section section : sections)
		{
			questionSectionBox.addItem(section.ordinal());
			questionSectionBox.setItemCaption(section.ordinal(), section.display());
		}

		questionSectionBox.setRequired(true);
		questionSectionBox.setWidth(200, UNITS_PIXELS);
		questionSectionBox.setHeight("-1px");
		questionSectionBox.setCaption(this.section);
		questionSectionBox.setNewItemsAllowed(true);
		questionSectionBox.setImmediate(true);
		questionSectionBox.setNewItemHandler(new NewItemHandler()
		{
			private static final long serialVersionUID = -319150467627276795L;

			@SuppressWarnings("synthetic-access")
			@Override
			public void addNewItem(String newSectionCaption)
			{
				AssessmentManagerGadget.this.newSectionCaption = newSectionCaption;
				List<AbstractEnumeration> sectionCodeSets = new ArrayList<AbstractEnumeration>(1);
				AssessmentManagerGadget.this.sectionCodeSets = sectionCodeSets;
				HierarchicalContainer container =  AssessmentManagerGadget.this.sections;
				Object newSectionObject = container.addItem();
				container.getContainerProperty(newSectionObject, "display").setValue(newSectionCaption);

				for (Object itemId : container.getItemIds())
				{
					Property property = container.getContainerProperty(itemId, "display");

					if (property != null && newSectionCaption.equalsIgnoreCase((String) property.getValue()))
					{
						questionSectionBox.select(itemId);
						break;
					}
				}
			}
		});

		questionSectionBox.setContainerDataSource(this.sections);
		questionSectionBox.setItemCaptionMode(ComboBox.ITEM_CAPTION_MODE_PROPERTY);
		questionSectionBox.setItemCaptionPropertyId("display");

		windowLayout.addComponent(questionSectionBox);

		questionSubsectionBox = new ComboBox();
		questionSubsectionBox.setFilteringMode(Filtering.FILTERINGMODE_CONTAINS);

		List<Subsection> subsections = Subsection.values();

		for (Subsection subsection : subsections)
		{
			questionSubsectionBox.addItem(subsection.ordinal());
			questionSubsectionBox.setItemCaption(subsection.ordinal(), subsection.display());
		}

		questionSubsectionBox.setRequired(false);
		questionSubsectionBox.setWidth(200, UNITS_PIXELS);
		questionSubsectionBox.setHeight("-1px");
		questionSubsectionBox.setCaption(this.subsection);
		questionSubsectionBox.setNewItemsAllowed(true);
		questionSubsectionBox.setImmediate(true);
		questionSubsectionBox.setNewItemHandler(new NewItemHandler()
		{
			private static final long serialVersionUID = -319150467627276795L;

			@SuppressWarnings("synthetic-access")
			@Override
			public void addNewItem(String newSubsectionCaption)
			{
				AssessmentManagerGadget.this.newSubsectionCaption = newSubsectionCaption;
				List<AbstractEnumeration> subsectionCodeSets = new ArrayList<AbstractEnumeration>(1);
				AssessmentManagerGadget.this.subsectionCodeSets = subsectionCodeSets;
				HierarchicalContainer container =  AssessmentManagerGadget.this.subsections;
				Object newSubsectionObject = container.addItem();
				container.getContainerProperty(newSubsectionObject, "display").setValue(newSubsectionCaption);
				container.getContainerProperty(newSubsectionObject, "parentSection").setValue(selectedSection);

				for (Object itemId : container.getItemIds())
				{
					Property property = container.getContainerProperty(itemId, "display");

					if (property != null && newSubsectionCaption.equalsIgnoreCase((String) property.getValue()))
					{
						questionSubsectionBox.select(itemId);
						break;
					}
				}
			}
		});

		questionSubsectionBox.setContainerDataSource(this.subsections);
		questionSubsectionBox.setItemCaptionMode(ComboBox.ITEM_CAPTION_MODE_PROPERTY);
		questionSubsectionBox.setItemCaptionPropertyId("display");

		windowLayout.addComponent(questionSubsectionBox);

Hello Kelly, just few days ago I implemented a very similar case. In my case were three ComboBoxes.

The first and the second filters so forth.

Well, here we go (remember this is my implementation):



// The first one.

public ComboBox getClientCombo() {
		
		if (clientCombo == null) {
			
			clientCombo = new ComboBox();
			clientCombo.setInputPrompt("Nenhum Client Selecionado");
			clientCombo.setItemCaptionPropertyId("nmClient");
			clientCombo.setItemCaptionMode(ItemCaptionMode.PROPERTY);
			clientCombo.setFilteringMode(FilteringMode.CONTAINS);
			clientCombo.setImmediate(true);
			clientCombo.setNullSelectionAllowed(false);
			
			fieldsLayout.addComponent(clientCombo, 1, 0);
			fieldsLayout.setComponentAlignment(clientCombo, Alignment.MIDDLE_LEFT);
			
		}
		
		return clientCombo;
	}

// the second

public ComboBox getVerticalCombo() {
		
		if (verticalCombo == null) {
			
			verticalCombo = new ComboBox();
			verticalCombo.setInputPrompt("Nenhuma Vertical Selecionada");
			verticalCombo.setItemCaptionPropertyId("nmVertical");
			verticalCombo.setItemCaptionMode(ItemCaptionMode.PROPERTY);
			verticalCombo.setFilteringMode(FilteringMode.CONTAINS);
			verticalCombo.setImmediate(true);
			verticalCombo.setNullSelectionAllowed(false);
			
			verticalCombo.setReadOnly(true);
			verticalCombo.setEnabled(false);
			
			fieldsLayout.addComponent(verticalCombo, 3, 0);
			fieldsLayout.setComponentAlignment(verticalCombo, Alignment.MIDDLE_LEFT);
			
		}
		
		return verticalCombo;
	}

// at last, third one

public ComboBox getOutputCombo() {
		
		if (outputCombo == null) {
			
			outputCombo = new ComboBox();
			outputCombo.setInputPrompt("Nenhuma Saída Selecionada");
			outputCombo.setItemCaptionPropertyId("nmOutput");
			outputCombo.setItemCaptionMode(ItemCaptionMode.PROPERTY);
			outputCombo.setFilteringMode(FilteringMode.CONTAINS);
			outputCombo.setImmediate(true);
			outputCombo.setNullSelectionAllowed(false);
			
			outputCombo.setReadOnly(true);
			outputCombo.setEnabled(false);
			
			fieldsLayout.addComponent(outputCombo, 5, 0);
			fieldsLayout.setComponentAlignment(outputCombo, Alignment.MIDDLE_LEFT);
			
		}

After that, we can implement the Property.ValueChangeListener in each one that mastering another.

e.g.:


private Property.ValueChangeListener getClientComboListener() {
		
		clientComboListener = new  Property.ValueChangeListener() {
			private static final long serialVersionUID = 1L;

			@Override
			public void valueChange(ValueChangeEvent event) {
				
				Object itemId = event.getProperty().getValue();
				
				String value = String.valueOf(getView().getClientCombo().getItem(itemId).getItemProperty("idClient").getValue());
				
				BeanItemContainer<Vertical> verticalsContainer = new BeanItemContainer<Vertical>(Vertical.class,
						getVerticals(new Integer(value)));
				
				getView().getVerticalCombo().setContainerDataSource(verticalsContainer);
				getView().getVerticalCombo().setReadOnly(false);
				getView().getVerticalCombo().setEnabled(true);
				
			}
			
		};
		
		return clientComboListener;
		
	}

If your first ComboBox has a pre-selected value, you can extract the code from the ValueChangeListener implementation and inclose it into another function and invoke in both situations, initially and when the value changes.

Notice that I have used a BeanItemContainer to populate each Combo.

Then, the collection that feed another combo is filtered by a property from his master.

Ready only and Enabled properties are optional.

Hope this helps :wink:

Thank you for the response, Vitor. Could you please show me what your getVerticals() method looks like? This looks like it should be just what I need!

Kelly, getVerticals() just returns a Collection which fill my BeanItemContainer. Observe that I pass a client id (selected in the first ComboBox) as argument what filters my query.

Any doubt, feel free to ask.

Thank you. I have the Collection, but I have not done of these before, so I was just going to use yours as a reference. I think I’m getting close. I’m sure I can find an example out on the web somewhere as well.

I think I’m on the right track now!

Okay, post if get success.

Will do, Vitor! Thank you again for the guidance. I am very new to Java and Vaadin so sometimes I need some assistance getting to the right thought process.

Here is what I ended up with:


		questionSectionBox.addListener(new ComboBox.ValueChangeListener()
		{
			@Override
			public void valueChange(ValueChangeEvent event) {
				if (questionSectionBox.getValue() != null)
				{
					Integer itemId = (Integer) event.getProperty().getValue();
					subsectionContainer = new BeanItemContainer<Subsection>(Subsection.class, getSubsectionsBySection(itemId));

					questionSubsectionBox.setContainerDataSource(subsectionContainer);
				}
			}

			@SuppressWarnings("synthetic-access")
			private Collection<? extends Subsection> getSubsectionsBySection(
					Integer sectionOrdinal) {
				List<Subsection> subsections = Subsection.values();
				ArrayList<Subsection> subsectionList = new ArrayList<Subsection>();

				for (Subsection subsection : subsections)
				{
					if (subsection.parentSection() == sectionOrdinal)
					{
						subsectionList.add(new Subsection(subsection.ordinal(), subsection.display(), subsection.parentSection()));
						questionSubsectionBox.setEnabled(true);
					} 
					else {
						questionSubsectionBox.setEnabled(false);
					}
				}
					return subsectionList;		
			}
		});

It appears to populate the correct number of items in the questionSubsectionBox, but the item caption is blank. I have setItemCaptionPropertyId to “display” and there is a property matching that name with the correct value in the BeanItemContainer. Any thoughts on how I can get the item caption to display correctly? It seems that’s the last step and I should be good to go.

Olá Kelly, could you post your bean and combo implementation code? Subsection, I mean.

Sure, here it is:


questionSubsectionBox = new ComboBox();
questionSubsectionBox.setFilteringMode(Filtering.FILTERINGMODE_CONTAINS);
questionSubsectionBox.setRequired(false);
questionSubsectionBox.setWidth(200, UNITS_PIXELS);
questionSubsectionBox.setHeight("-1px");
questionSubsectionBox.setCaption(this.subsection);
questionSubsectionBox.setNewItemsAllowed(true);
questionSubsectionBox.setImmediate(true);
questionSubsectionBox.setNewItemHandler(new NewItemHandler()
{
	private static final long serialVersionUID = -319150467627276795L;

	@SuppressWarnings("synthetic-access")
	@Override
	public void addNewItem(String newSubsectionCaption)
	{
		AssessmentManagerGadget.this.newSubsectionCaption = newSubsectionCaption;
		List<AbstractEnumeration> subsectionCodeSets = new ArrayList<AbstractEnumeration>(1);
		AssessmentManagerGadget.this.subsectionCodeSets = subsectionCodeSets;
		HierarchicalContainer container =  AssessmentManagerGadget.this.subsections;
		Object newSubsectionObject = container.addItem();
		container.getContainerProperty(newSubsectionObject, "display").setValue(newSubsectionCaption);
		container.getContainerProperty(newSubsectionObject, "parentSection").setValue(selectedSection);

		for (Object itemId : container.getItemIds())
		{
			Property property = container.getContainerProperty(itemId, "display");

			if (property != null && newSubsectionCaption.equalsIgnoreCase((String) property.getValue()))
			{
				questionSubsectionBox.select(itemId);
				break;
			}
		}
	}
});
	
		questionSectionBox.addListener(new ComboBox.ValueChangeListener()
		{
			@Override
			public void valueChange(ValueChangeEvent event) {
				if (questionSectionBox.getValue() != null)
				{
					Integer itemId = (Integer) event.getProperty().getValue();
					subsectionContainer = new BeanItemContainer<Subsection>(Subsection.class, getSubsectionsBySection(itemId));

					questionSubsectionBox.setContainerDataSource(subsectionContainer);
				}
			}

			@SuppressWarnings("synthetic-access")
			private Collection<? extends Subsection> getSubsectionsBySection(
					Integer sectionOrdinal) {
				// TODO Auto-generated method stub
				List<Subsection> subsections = Subsection.values();
				ArrayList<Subsection> subsectionList = new ArrayList<Subsection>();

				for (Subsection subsection : subsections)
				{
					if (subsection.parentSection() == sectionOrdinal)
					{
						subsectionList.add(new Subsection(subsection.ordinal(), subsection.display(), subsection.parentSection()));
						questionSubsectionBox.setEnabled(true);
					} 
					else {
						questionSubsectionBox.setEnabled(false);
					}
				}
					return subsectionList;		
			}
		});

		questionSubsectionBox.setItemCaptionMode(ComboBox.ITEM_CAPTION_MODE_PROPERTY);
		questionSubsectionBox.setItemCaptionPropertyId("display");

		windowLayout.addComponent(questionSubsectionBox);

Perhaps you agree that it is interesting to add the following line under the .setImmediate line:

questionSubsectionBox.setItemCaptionPropertyId("a bean attribute like a friendly name for the subsection");

It is part of the ComboBox configuration.

Is line 75 of my previous post not accomplishing what I believe it to be? Here is the content of the beanItemContainer that is set as the data source:

[Subsection: [{ordinal=0, display=Radiologist Subsection, parentSection=1}]
]

My intention is for line 75 above to pull the display attribute to use as the item caption. This is the same way I am getting the item caption for my other ComboBoxes that are pulling data from the database instead of the beanItemContainer.

Apologies by my mistake at previous post.

Well, look at line 74 of your code, try to change to ItemCaptionMode.PROPERTY that one is deprecated.

JavaDoc says:

“Item caption mode: Item captions are read from property specified with setItemCaptionPropertyId.”

on the suggestion above.

I think that may be for Vaadin 7. I didn’t specify before, I apologize, but I am using Vaadin 6.8.10. I tried your suggestion and it was not recognized.

In some of my other ComboBoxes, I have a for loop that cycles through the list of values and adds them and their caption to the ComboBox. Since I didn’t see that in your (Vitor) code I thought perhaps I didn’t need to do that for a bean item container. This is my first experience with these, so I could have missed something.

Here’s what I’m doing for my section box:


		for (Section section : sections)
		{
			questionSectionBox.addItem(section.ordinal());
			questionSectionBox.setItemCaption(section.ordinal(), section.display());
		}

One reason that I was thinking that maybe I didn’t need to do this is because it appears that the correct number of spaces are in the drop down, but just the caption is missing.

Yes It is! Sorry me too. I could have asked.

Kelly, it is my lunch hour. After, I will copy your code and implement trying reproduce the problem. See you later.

Thank you, Vitor! I am very appreciative of your assistance!

I am debugging and trying to figure out where I’m going wrong and have found that the allItemIds under items in the questionSubsectionBox lists this:

[Subsection: [{ordinal=0, display=Radiologist Subsection, parentSection=1}]
]

and the ComboBoxes that are working have a set of number, such as:

[3, 1, 0, 4, 5, 2]

There is also a section under items with a HashTable of items mapped to their display values on the working ones that is not on the failing one. These look like this:

5={display=Technical - Viewers}, 4={display=Technical - Server}, 3={display=PACS Admin Workflow}, 2={display=Technologist Workflow}, 1={display=Radiologist Workflow}, 0={display=Stakeholder Interview}}

It seems like all of the data is being added as an item id, but items themselves aren’t being added or something along those lines. I think that whatever the function should be that would add the items from the BeanItemContainer to the ComboBox is not doing so appropriately.

Only now I read your post about that loop where you set the caption for each item. No, it is not necessary. Look, you have a bean, the bean has an attribute which you use as item caption property id.

I am doing some tests, at this moment is time to remove that loop and observe the combobox’s behaviour.

Another suggestion: You bring the whole data of subsection and then mount a sublist list with “filtered” data. Why not filter the data in the first time, generating a filtered list when search data?

Vitor - I don’t have that loop in the particular box I’m working on, so I can’t remove it. That is an example from another box that is filled by selecting objects from a List.

I’ve thought about filtering, but I’m unsure how to approach it. I haven’t been able to find anything yet that would allow me to do this on a ComboBox. I see how to do it for a table, but haven’t been able to apply that to the contents of the ComboBox.