Field.java
package no.ntnu.idatt1002.demo.view.components;
import java.util.List;
import java.util.Map;
import javax.swing.event.ChangeListener;
import org.controlsfx.control.CheckComboBox;
import org.controlsfx.control.SearchableComboBox;
import javafx.collections.ListChangeListener;
import javafx.scene.Node;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.text.Text;
import no.ntnu.idatt1002.demo.Logger;
import no.ntnu.idatt1002.demo.data.Storable;
/**
* A field for a form.
*
* <p>
* The field class is responsible for rendering the correct input field based on
* the type of the field.
* </p>
*
* <p>
* There are three types of fields:
* <ul>
* <li>NUMBER: A field for numbers. Returns the number typed</li>
* <li>STRING: A field for strings. Returns the string typed</li>
* <li>LIST: A field for selecting from a list of options. Returns the index of
* the selected item</li>
* </ul>
*
* All fields have a placeholder that is displayed when the field is empty.
*/
public class Field<T> {
/**
* Enum for the different types of fields.
*/
public enum FieldType {
NUMBER,
STRING,
LIST,
MAP
}
/**
* Create a new field of type number.
*
* @return a new field of type number.
*/
public static Field<Number> ofNumber(String placeholder) {
return new Field<Number>(FieldType.NUMBER, placeholder);
}
public static Field<String> ofString(String placeholder) {
return new Field(FieldType.STRING, placeholder);
}
/**
* Create a new field of type list.
*
* @return a new field of type list.
*/
public static Field<Integer> ofList(String placeholder, List<? extends Storable> options) {
return new Field<Integer>(FieldType.LIST, placeholder, options);
}
public static Field<Integer[]> ofMap(String placeholder, Map<Integer, ? extends Storable> options) {
return new Field<Integer[]>(FieldType.MAP, placeholder, options);
}
private FieldType type;
private String placeholder;
private T value;
private Map<Integer, ? extends Storable> mapOptions;
private List<? extends Storable> options;
private String name;
private VBox renderedField;
public Node getRenderedField() {
return renderedField;
}
public FieldType getType() {
return type;
}
public T getValue() {
return value;
}
public Field<T> setValue(T value) {
this.value = value;
return this;
}
private Field(FieldType type, String placeholder) {
this.type = type;
this.placeholder = placeholder;
render();
}
private Field(FieldType type, String name, Map<Integer, ? extends Storable> options) {
this.type = type;
this.name = name;
this.mapOptions = options;
render();
}
private Field(FieldType type, String name, List<? extends Storable> options) {
this.type = type;
this.name = name;
this.options = options;
render();
}
@SuppressWarnings("unchecked")
private void render() {
if (renderedField == null) {
renderedField = new VBox();
}
renderedField.getChildren().clear();
switch (type) {
case NUMBER:
NumberField field = new NumberField(v -> {
this.value = (T) (Number) v;
});
field.setPromptText(placeholder);
renderedField.getChildren().add(field);
break;
case STRING:
renderedField.getChildren().add(new InputField(placeholder).setOnSubmit(value -> {
this.value = (T) value;
}).setOnChange(value -> {
this.value = (T) value;
}));
break;
case MAP:
DropdownMultiselect dropdown = new DropdownMultiselect(mapOptions);
dropdown.setPrefHeight(35);
dropdown.setPrefWidth(200);
dropdown.getCheckModel().getCheckedItems().addListener(new ListChangeListener() {
public void onChanged(ListChangeListener.Change c) {
value = (T) dropdown.getCheckModel().getCheckedItems().stream()
.map(s -> s.getId())
.toArray();
}
});
renderedField.getChildren().addAll(new Text(name), dropdown);
break;
case LIST:
SearchableComboBox<Storable> searchableComboBox = new SearchableComboBox<>();
searchableComboBox.setPrefSize(200, 35);
searchableComboBox.setPromptText(name);
searchableComboBox.getItems().addAll(options);
searchableComboBox.valueProperty().addListener((obs, oldVal, newVal) -> {
if (newVal == null) {
return;
}
value = (T) (Integer) newVal.getId();
});
renderedField.getChildren().addAll(searchableComboBox);
break;
default:
break;
}
}
}