All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.databasesandlife.util.wicket.HierarchicalMultipleChoice Maven / Gradle / Ivy

There is a newer version: 21.0.1
Show newest version
package com.databasesandlife.util.wicket;

import org.apache.wicket.markup.html.form.ListMultipleChoice;
import org.apache.wicket.model.IModel;
import org.apache.wicket.util.string.AppendingStringBuffer;

import javax.annotation.Nonnull;
import java.io.Serializable;
import java.util.*;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static java.lang.System.arraycopy;
import static java.util.Collections.emptyList;

/**
 * Given a list of objects like "a", "b:c", "b:d" displays a hierarchical list.
 *    

* Displays a list such as: *

 *     a
 *     b
 *       c
 *       d
 * 
* The user may then either select individual items such as "c" or "d", or parents such as "b". * Selecting a parent results in all children being selected in the underlying model. * In case the component is displayed with all children of a parent being pre-selected, the parent is shown as selected in the list. *

* If the user does not select anything, the model is filled with the empty list (not null). * The model can contain either null or the empty list to represent the user not selecting anything, although empty list is preferred. */ public class HierarchicalMultipleChoice extends ListMultipleChoice> { protected static class Choice implements Serializable { /** For example "b" or "b:c" */ protected String path; protected List items; public Choice(String path, List items) { this.path = path; this.items = items; } public int getIndentPixels() { String[] components = path.split(Pattern.quote(":")); return 8 + 15 * (components.length - 1); } @Override public String toString() { var colon = path.lastIndexOf(":"); if (colon == -1) return path; else return path.substring(colon + 1); } } protected class SelectionModel implements IModel>> { protected IModel> underlyingModel; public SelectionModel(IModel> u) { this.underlyingModel = u; } @Override public List> getObject() { var individuals = new ArrayList<>(Optional.ofNullable(underlyingModel.getObject()).orElse(emptyList())); var result = new ArrayList>(); for (var c : choices) { if (individuals.containsAll(c.items)) { result.add(c); individuals.removeAll(c.items); } } return result; } @Override public void setObject(Collection> object) { var result = new HashSet(); for (var c : object) result.addAll(c.items); underlyingModel.setObject(new ArrayList<>(result)); } @Override public void detach() { underlyingModel.detach(); } } protected class ChoicesModel implements IModel>> { @Override public List> getObject() { if (hideChildrenOfSelection) return choices.stream() .filter(c -> selectionModel.getObject().stream().noneMatch(sel -> c.path.startsWith(sel.path + ":"))) .collect(Collectors.toList()); else return choices; } } protected SelectionModel selectionModel; protected List> choices; protected boolean hideChildrenOfSelection; /** For example with "a:b" and maxLevel=1 returns "a" */ protected @Nonnull String getLevel(@Nonnull String[] pathComponents, int maxLevel) { String[] result = new String[maxLevel]; arraycopy(pathComponents, 0, result, 0, maxLevel); return String.join(":", result); } public HierarchicalMultipleChoice( @Nonnull String wicketId, @Nonnull IModel> model, @Nonnull List leafNodes, @Nonnull Function toPath ) { super(wicketId); var choiceStrings = new TreeSet(); // for input [a, b:c, b:d], all levels are [a, b, b:c, b:d] for (var leaf : leafNodes) { String[] components = toPath.apply(leaf).split(Pattern.quote(":")); for (var level = 1; level <= components.length; level++) choiceStrings.add(getLevel(components, level)); } this.choices = choiceStrings.stream() .map(path -> new Choice<>( path, leafNodes.stream().filter(x -> toPath.apply(x).startsWith(path)).toList())) .toList(); setChoiceRenderer(null); setChoices(new ChoicesModel()); setModel(selectionModel = new SelectionModel(model)); } /** * Remove children of selected entries. *

* The Chosen JS library removes selected elements from the drop-down. * This means that when a parent is selected, the children remain, which makes them appear visually under other parents. * This option removes children of selected parents. * This requires the page to be refreshed every time the selection changes. */ public @Nonnull HierarchicalMultipleChoice setHideChildrenOfSelection(boolean hide) { this.hideChildrenOfSelection = hide; return this; } @Override protected void setOptionAttributes(AppendingStringBuffer buffer, Choice choice, int index, String selected) { super.setOptionAttributes(buffer, choice, index, selected); buffer.append(" style='padding-left: " + choice.getIndentPixels() + "px'"); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy