0

This feels like a pretty standard question and has probably been asked before, but I found it hard to find because it is hard to define in words. So if this is a duplicate, go ahead and redirect me!

I'm using Vaadin to build this web app, but that shouldn't matter to the problem at hand, unless there is an even better way of solving this through some Vaadin magic.

I have three classes:

  • Table
  • FilterGenerator
  • Container

My "design" looks like this:

  • The Container adds some properties (column headers) to itself in its constructor
  • The FilterGenerator @Inject the Container (in order to use the Container's getDistinct() method that gets the distinct items from the container - in order to present them nicely in a ComboBox in the filter)
  • The Table @Inject the FilterGenerator (in order to table.setFilterGenerator(filterGenerator))
  • The Table @Inject the Container and calls the containers addItems() method to add items to the container
  • The Table then adds the container as a datasource

What happens?

What I should have now is a table with a ComboBox in the column header, presenting distinct values to filter.

What I get is a table with a ComboBox in the column header, presenting nothing in the ComboBox, because there are no items in the ComboBox.

This is not surprising, because when the FilterGenerator calls the Containers getDistinct() method, it will get an empty map of <Column, items> back, because at the time of @Inject in the FilterGenerator, the Table hasn't called the Containers addItems() method, so the Container will at this moment be empty.

The question

How should I design this application if I want a component (FilterTable) to get something from a second component (Container), when a third component (Table) is @Inject-ing both forementioned components and it is crucial that the second component (Container) already has been initialized when the first component (FilterGenerator) gets something from it?

I could:

  • In the Table, simply create a new FilterGenerator. This would work, but it isn't very nice. For example, what happens if some other component wants to use the FilterGenerator?

  • Go back to xml-configuration in order to "manually" create the instances in the correct order. This would probably work (if I remember correctly), but having instance creation depending on the order of the elements in your xml file doesn't sound very good to me.

  • Use "programmatic injection" by using the ApplicationContext.getBean() in code. This would probably be even worse than the above alternatives?

Does anyone have any good suggestions on how to solve this triangular drama?

Here is the relevant code:

The Table

@Component
@Scope("session")
public class SampleAppMainTable extends FilteringTable {

    @Inject
    private SampleAppMainTableContainer sampleAppMainTableContainer;
    @Inject
    private SampleAppService sampleAppService;
    @Inject
    private SampleAppMainTableFilterGenerator sampleAppMainTableFilterGenerator;

    public SampleAppMainTable() {
        //...setting up the table
    }

    @PostConstruct
    public void PostConstruct() throws GeneralSecurityException {
        addMainTableItems();
        setupMainTable();
    }

    public void setupMainTable() {
        this.setFilterGenerator(sampleAppMainTableFilterGenerator);
        sampleAppMainTableFilterGenerator.getCustomFilterComponent("Sample Id");
        this.setContainerDataSource(sampleAppMainTableContainer);
    }

    public void addMainTableItems() {
            sampleAppMainTableContainer.addItemsToContainer(sampleAppService.getAllSamples());
    }
}

The Container

@Component
@Scope("prototype")
public class SampleAppMainTableContainer extends IndexedContainer {

    public void addItemsToContainer(List<Sample> samples) {
        // adding items to the container...
    }

    public Map<String, List<String>> getDistinctProperties() {
        // extracting distinct items from the table...
    }
}

The FilterGenerator

@Component
@Scope("session")
public class SampleAppMainTableFilterGenerator implements FilterGenerator {

    @Inject
    SampleAppMainTableContainer sampleAppMainTableContainer;

    private List<String> aList = null;

    @Override
    public AbstractField<?> getCustomFilterComponent(Object propertyId) {

        Map<String, List<String>> map = new HashMap<String, List<String>>();

        map = sampleAppMainTableContainer.getDistinctProperties();

        if (propertyId.equals("Sample Id")) {
            ComboBox sampleIdCB = new ComboBox();
            BeanItemContainer<String> dataList = new BeanItemContainer<String>(String.class);
            List<String> aList = map.get("Sample Id");
            dataList.addAll(aList);
            sampleIdCB.setContainerDataSource(dataList);
            sampleIdCB.setImmediate(true);
            return sampleIdCB;
        }

        return null;
    }

    // other overridden methods needed...
}
Roger
  • 2,684
  • 4
  • 36
  • 51
  • 1
    Isn't your problem simply the fact that your `FilterGenerator` is stateful, where it should be stateless? – M. Deinum Sep 27 '13 at 10:53
  • Thanks for your reply! I'm not entirely sure what you mean by stateless. My FilterGenerator class is just a simple class that happens to implement a (Vaadin addon) FilterGenerator. What it does is @Override a couple of methods that generate the desired filters to be used in my Table. My problem relates to the fact that the Container is not fully initialized at the creation of the FilterGenerator. Should I create a `new FilterGenerator(container)` in the Table class, everything would work like a charm. – Roger Sep 27 '13 at 11:35
  • I find it hard to understand your setup from your description. Can you provide some code snippets that show what you do and where? – Sven Amann Sep 27 '13 at 11:36
  • I'm not familiar with Vaadin, but from your description it sounds like you are creating a Filter that holds state (it calls a certain method once and caches the results) whereas is, I guess, should always call the method when needed. – M. Deinum Sep 27 '13 at 11:41
  • Did you implement the filter yourself? You only talk about the FilterGenerator... Are you sure that the same Container-Instance is injected both times? Depending on the Inject-Framework you use, you need to explicitly configure whether a new instance is created for every injection or a singleton is used. – Sven Amann Sep 27 '13 at 11:45

1 Answers1

0

I think you problem is that you do processing logic during the the injection phase. You should wait till everything is set up and then do the processing. You can do something like this by moving the processing logic from the constructor to an initialization method and marking this method with @Inject. By definition injection is done last on methods, i.e., at the time the method gets called by the injector, all the fields are injected.

Sven Amann
  • 565
  • 2
  • 12