Dynamic <optgroup> support in wicket
Asked Answered
F

3

5

I'm looking to render a <select> tag in my page using wicket, but group the options with <optgroup>, this was discussed on Separator in a Wicket DropDownChoice, but in the solutions there the <optgroup> assume that the <optgroup> tags are static, I'm wanting to pull both the options and the groups from a database.

Francoise answered 15/12, 2011 at 14:4 Comment(0)
J
4

Use two nested repeaters to iterate over your groups and options:

<select wicket:id="select">
    <optgroup wicket:id="group">
        <option wicket:id="option"></option>
    </optgroup>
</select>
Jeaninejeanlouis answered 15/12, 2011 at 15:50 Comment(1)
But will the form component (wicket:id="select") still get populated with the selected option when the form is submitted?Francoise
T
3

I have had basically the same problem. After a few days looking for a short solution, I believe what works best, for maximum flexibility, is using repeaters, containers and AttributeModifier, something like:

<select wicket:id="select">
    <wicket:container wicket:id="repeatingView">
        <optgroup wicket:id="optGroup">
          <wicket:container wicket:id="selectOptions">
            <option wicket:id="option"></option>
          </wicket:container>
        </optgroup>
    </wicket:container>
</select>

In Java code, "select" is a Select; "repeatingView" is a RepeatingView. Nested inside the RepeatingView there's a WebMarkupContainer named by .newChildId(). Nested inside is another WebMarkupContainer that represents "optGroup". Inside this second WMC are an AttributeModifier that adds a dynamic label to the optgroup, and a SelectOptions that processes "selectOptions" and "option". Something like:

Select select = new Select("select");
add(select);

RepeatingView rv = new RepeatingView("repeatingView");
select.add(rv);

for(String groupName : groupNames){

    WebMarkupContainer overOptGroup = new WebMarkupContainer(rv.newChildId());
    rv.add(overGroup);

    WebMarkupContainer optGroup = new WebMarkupContainer("optGroup");
    overOptGroup.add(optGroup);
    optGroup.add(
        new AttributeModifier("label",true,new Model<String>(groupName))
    );
    optGroup.add(
        new SelectOptions<MyBean>(
            "selectOptions",listOfBeanOptionsForThisGroup,new MyBeanRenderer()
        )
    );
}

(this is supposing Strings are passed directly as group names and that the options refer to beans of type MyBean, listed in the variable listOfBeanOptionsForThisGroup)

I suppose it shouldn't be hard to refactor this solution into something that uses much less nesting, if anyone's got suggestions, I'll edit them into the answer and credit them. Using ListView instead of RepeatingView should also reduce code size.

Tantalite answered 3/5, 2012 at 13:13 Comment(1)
The "repeatingView" WebMarkupContainer can be removed and use the "optGroup" as repeating view instead, making it simplerReverberate
F
2

Ok, so at the moment my solution is to have something like this:

   interface Thing {
       String getCategory();
   }

and then:

            List<Thing> thingList = service.getThings();
    DropDownChoice<Thing> dropDownChoice = new DropDownChoice<Thing>("select",
            thingList) {
        private static final long serialVersionUID = 1L;
        private Thing last;

        private boolean isLast(int index) {
            return index - 1 == getChoices().size();
        }

        private boolean isFirst(int index) {
            return index == 0;
        }

        private boolean isNewGroup(Thing current) {
            return last == null
                    || !current.getCategory().equals(last.getCategory());
        }

        private String getGroupLabel(Thing current) {
            return current.getCategory();
        }

        @Override
        protected void appendOptionHtml(AppendingStringBuffer buffer,
                Thing choice, int index, String selected) {
            if (isNewGroup(choice)) {
                if (!isFirst(index)) {
                    buffer.append("</optgroup>");
                }
                buffer.append("<optgroup label='");
                buffer.append(Strings.escapeMarkup(getGroupLabel(choice)));
                buffer.append("'>");
            }
            super.appendOptionHtml(buffer, choice, index, selected);
            if (isLast(index)) {
                buffer.append("</optgroup>");
            }
            last = choice;

        }
    };

This requires that thingList is already sorted based on the category.

Francoise answered 15/12, 2011 at 15:44 Comment(1)
This almost seems a little hacky when Wicket provides Select, SelectOption and SelectOptions classes (like userBigNum's answer uses) specifically for cases like optgroup. But when I implemented a solution using those classes, what I found is that you lose some of the niceties that are baked into DropDownChoice - more specifically, it doesn't render a "Choose One" option for you and it takes an IOptionRenderer instead of a ChoiceRenderer. The approach you've come up with may very well be the better of the two.Gilbertogilbertson

© 2022 - 2024 — McMap. All rights reserved.