InputDialog.java
/*
** Module : InputDialog.java
** Abstract : Generic input data dialog.
**
** Copyright (c) 2017-2022, Golden Code Development Corporation.
**
** -#- -I- --Date-- --------------------------------Description-----------------------------------
** 001 HC 20170612 Initial version.
** 002 EVL 20170926 Javadoc fix.
** 003 OM 20190111 Got rid of compile warning related to ambiguous method call.
** 004 TJD 20220331 Removed deprecated Inputdialog.showFilterDialog()
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.admin.client.widget.dialog;
import com.goldencode.p2j.admin.client.widget.*;
import com.google.gwt.dom.client.*;
import com.google.gwt.editor.client.*;
import com.google.gwt.event.dom.client.*;
import com.google.gwt.uibinder.client.*;
import com.google.gwt.user.client.ui.*;
import com.google.inject.*;
import com.google.web.bindery.event.shared.*;
import org.gwtbootstrap3.client.ui.Button;
import org.gwtbootstrap3.client.ui.CheckBox;
import org.gwtbootstrap3.client.ui.*;
import org.gwtbootstrap3.client.ui.IntegerBox;
import org.gwtbootstrap3.client.ui.LongBox;
import org.gwtbootstrap3.client.ui.SuggestBox;
import org.gwtbootstrap3.client.ui.TextArea;
import org.gwtbootstrap3.client.ui.TextBox;
import org.gwtbootstrap3.client.ui.base.*;
import org.gwtbootstrap3.client.ui.base.ValueBoxBase;
import org.gwtbootstrap3.client.ui.constants.*;
import org.gwtbootstrap3.client.ui.form.validator.*;
import org.gwtbootstrap3.client.ui.html.*;
import org.gwtbootstrap3.extras.select.client.ui.*;
import org.gwtbootstrap3.extras.select.client.ui.constants.*;
import java.util.*;
import java.util.function.*;
/**
* Generic input data dialog.
*/
public class InputDialog
extends Composite
{
/**
* GWT binder.
*/
interface Binder
extends UiBinder<Modal, InputDialog>
{}
/** Widget */
@UiField
HTML bodyMessage;
/** Widget */
@UiField
org.gwtbootstrap3.client.ui.html.Span title;
/** Widget */
@UiField
Button bSave;
/** Widget */
@UiField
Button bCancel;
/** Widget */
@UiField
Modal modal;
/** Widget */
@UiField
ModalFooter modalFooter;
/** Widget */
@UiField
ButtonGroup extraButtonGroup;
/** Widget */
@UiField
Div formsPanel;
/** Widget */
@UiField
NavTabs navTabs;
/** Size of the labels in the first column of fields. */
private int labelSize = 4;
/** Ato hide flag */
private boolean autoHide;
/** The handler that will be closed when the dialog is dismissed */
private Consumer<Map<Field, Object>> actionHandler;
/** Hide dialog handler registration */
private HandlerRegistration hideHandlerReg;
/** Map of fields to widgets */
private Map<Field, HasValue> widgets = new HashMap<>();
/** Mao of panel forms */
private Map<Panel, Form> forms = new HashMap<>();
/** Map of tabs */
private Map<Panel, AnchorListItem> tabs = new HashMap<>();
/** Currently displayed form */
private Form activeForm;
/** Currently displayed panel */
private Panel activePanel;
/** Text translation map */
private Map<String, String> translationMap;
/** If <code>true</code> the dialog will show the top level panels as tabs */
private boolean tabbed;
/**
* Ctor.
*
* @param binder
* GWT binder.
*/
@Inject
InputDialog(InputDialog.Binder binder)
{
initWidget(binder.createAndBindUi(this));
}
/**
* Build the dialog.
*
* @param title
* Dialog title.
* @param items
* Input form items.
*/
public void build(String title, Item... items)
{
build(title, "", items);
}
/**
* Build the dialog.
*
* @param title
* Dialog title.
* @param helpText
* Help text to be displayed above the table or <code>null</code>
* @param items
* Input form items.
*/
public void build(String title, String helpText, Item... items)
{
this.title.setText(title);
bodyMessage.setHTML(helpText);
if (!hasLabels(items))
{
labelSize = 0;
}
buildWidgets(items);
layout(items);
for (Map.Entry<Panel, AnchorListItem> anchor : tabs.entrySet())
{
anchor.getValue().addClickHandler(event ->
{
Panel newPanel = anchor.getKey();
if (newPanel == activePanel)
{
return;
}
AnchorListItem oldTab = tabs.get(activePanel);
AnchorListItem newTab = anchor.getValue();
Form newForm = forms.get(anchor.getKey());
if (oldTab != null)
{
oldTab.setActive(false);
}
newTab.setActive(true);
if (activeForm != null)
{
activeForm.setVisible(false);
}
newForm.setVisible(true);
activePanel = newPanel;
activeForm = newForm;
});
}
}
/**
* Adds extra buttons.
*
* @param buttons
* Extra buttons array.
*/
public void addButtons(Button ... buttons)
{
for (Button button : buttons)
{
extraButtonGroup.add(button);
}
}
/**
* Adds field validators.
*
* @param f
* Field.
* @param validators
* Array of validators.
*/
public void addValidators(Field f, Validator... validators)
{
for (Validator v : validators)
{
((HasValidators) widgets.get(f)).addValidator(v);
}
}
/**
* Allows to override the default label of the save button.
*
* @param saveButtonText
* The new label for the save button
*/
public void setSaveButtonText(String saveButtonText)
{
this.bSave.setText(saveButtonText);
}
/**
* Sets the default label size.
*
* @param size
* New size.
*/
public void setLabelSize(int size)
{
if (size < 1 || size > 12)
{
throw new RuntimeException("Unsupported label size!");
}
labelSize = size;
}
/**
* Sets the tabbed flag.
*
* @param tabbed
* Tabbed flag.
*/
public void setTabbed(boolean tabbed)
{
this.tabbed = tabbed;
}
/**
* Returns the widget of a specified field.
*
* @param f
* A field.
*
* @return the widget or <code>null</code> if widget not found
*/
public HasValue getWidget(Field f)
{
return widgets.get(f);
}
/**
* Returns the value of a specified field.
*
* @param field
* A field.
* @param defValue
* Value that will be returned when the field value is <code>null</code>.
* @param <T>
* Value type.
*
* @return The field value
*/
public <T> T getValue(Field field, T defValue)
{
HasValue w = getWidget(field);
if (w == null)
{
throw new RuntimeException("Field does not exist!");
}
T v = (T) w.getValue();
return v == null ? defValue : v;
}
/**
* Sets field value.
*
* @param field
* A field.
* @param value
* A value.
*/
public void setValue(Field field, Object value)
{
setValue(field, value, false);
}
/**
* Sets field value.
*
* @param field
* A field.
* @param value
* A value.
* @param fireEvents
* When <code>true</code> the widget will trigger a value-change event.
*/
public void setValue(Field field, Object value, boolean fireEvents)
{
HasValue w = getWidget(field);
if (w == null)
{
throw new RuntimeException("Field does not exist!");
}
w.setValue(value, fireEvents);
}
/**
* Get tab widget for the supplied panel.
*
* @param p
* A panel.
*
* @return tab widget
*/
public AbstractListItem getTab(Panel p)
{
return tabs.get(p);
}
/**
* Show the dialog. The dialog will close automatically when Save clicked.
*
* @param parent
* Parent widget where this widget will be revealed in.
* @param actionHandler
* Invoked when Save or Cancel button is clicked.
*/
public void show(HasWidgets parent, Consumer<Map<Field, Object>> actionHandler)
{
show(parent, true, actionHandler);
}
/**
* Show the dialog.
*
* @param parent
* Parent widget where this widget will be revealed in.
* @param autoHide
* When true, the dialog will hide when Save is clicked automatically.
* When false, dismiss() must be called by the user code to make the dialog go away.
* Note that the dialog always hides when Cancel is clicked.
* @param actionHandler
* Invoked when Save or Cancel button is clicked.
*/
public void show(HasWidgets parent, boolean autoHide, Consumer<Map<Field, Object>> actionHandler)
{
this.autoHide = autoHide;
this.actionHandler = actionHandler;
parent.add(this);
boolean activeModal = Document.get().getBody().hasClassName("modal-open");
hideHandlerReg = modal.addHiddenHandler(evt ->
{
this.removeFromParent();
hideHandlerReg.removeHandler();
if (activeModal)
{
// when stack of modals are displayed the modal-open style must be restored
// so that the modal under this one has the correct appearance (for example
// the scroll bars are displayed and the modal can be scrolled when larger
// than the visible area)
Document.get().getBody().addClassName("modal-open");
}
});
modal.show();
}
/**
* Dismisses the dialog.
*/
public void dismiss()
{
modal.hide();
}
/**
* Click event handler.
*
* @param event
* Click event.
*/
@UiHandler("bSave")
public void bSaveClick(ClickEvent event)
{
boolean valid = true;
for (Form form : forms.values())
{
boolean formValid = form.validate(true);
if (tabs != null && !tabs.isEmpty())
{
for (Map.Entry<Panel, Form> panel : forms.entrySet())
{
if (panel.getValue() == form)
{
AnchorListItem anchor = tabs.get(panel.getKey());
if (anchor != null)
{
anchor.setIcon(formValid ? null : IconType.EXCLAMATION_TRIANGLE);
if (!formValid)
{
anchor.addStyleName("has-error");
}
else
{
anchor.removeStyleName("has-error");
}
}
}
}
}
valid &= formValid;
}
if (!valid)
{
return;
}
Map<Field, Object> result = new HashMap<>();
widgets.forEach((f, w) ->
{
result.put(f, w.getValue());
});
if (autoHide)
{
dismiss();
}
actionHandler.accept(result);
}
/**
* Click event handler.
*
* @param event
* Click event.
*/
@UiHandler("bCancel")
public void bCancelClick(ClickEvent event)
{
if (actionHandler != null)
{
dismiss();
actionHandler.accept(null);
}
}
/**
* Text translation map.
*
* @return the translation map
*/
public Map<String, String> getTranslationMap()
{
return translationMap;
}
/**
* Setups the translation map that holds pairs of a key and its presentation value.
*
* @param translationMap
* The translation map
*/
public void setTranslationMap(Map<String, String> translationMap)
{
this.translationMap = translationMap;
}
/**
* Builds the dialog widgets from the input dialog model.
*
* @param items
* The input dialog model.
*/
private void buildWidgets(Item[] items)
{
for (Item item : items)
{
if (item instanceof WidgetItem)
{
continue;
}
if (item instanceof Group)
{
buildWidgets(((Group) item).subItems);
continue;
}
Field f = (Field) item;
if (f.value == null)
{
// the field type is determined from the value, so no null allowed
throw new RuntimeException("Value must not be null!");
}
Class clazz = f.value instanceof Class ? (Class) f.value : f.value.getClass();
HasValue rootWidget;
if (f instanceof SelectField || f instanceof MultiSelectField)
{
boolean multi = f instanceof MultiSelectField;
SelectBase select = multi ?
new MultipleSelectWithValidation() :
new SelectWithValidation();
for (Object opt : f.options)
{
select.add((IsWidget) opt);
}
if (f.value != null)
{
select.setValue(f.value);
}
if (f.options.size() > 10)
{
select.setLiveSearch(true);
select.setLiveSearchNormalize(true);
select.setLiveSearchStyle(LiveSearchStyle.CONTAINS);
select.setLiveSearchPlaceholder("Search");
}
if (f.header != null)
{
select.setHeader(f.header);
}
rootWidget = select;
}
else if (clazz == String.class)
{
com.google.gwt.user.client.ui.ValueBoxBase<String> w;
if (f.lines > 0)
{
w = new TextArea();
((TextArea) w).setVisibleLines(f.lines);
}
else
{
w = new TextBox();
}
if (f.password)
{
w.getElement().setPropertyString("type", "password");
}
if (f.value instanceof String)
{
w.setValue((String) f.value);
}
rootWidget = w;
if (f.suggestions != null && !f.suggestions.isEmpty())
{
MultiWordSuggestOracle oracle = new MultiWordSuggestOracle();
oracle.addAll(f.suggestions);
oracle.setDefaultSuggestionsFromText(f.suggestions);
SuggestBox sb = new SuggestBox(oracle, (ValueBoxBase<String>) w);
rootWidget = sb;
}
}
else if (clazz == Integer.class)
{
IntegerBox w = new IntegerBox();
if (f.value instanceof Integer)
{
w.setValue((Integer) f.value);
}
rootWidget = w;
}
else if (clazz == Long.class)
{
LongBox w = new LongBox();
if (f.value instanceof Long)
{
w.setValue((Long) f.value);
}
rootWidget = w;
}
else if (clazz == Boolean.class)
{
InlineCheckBox w = new InlineCheckBox();
w.setText(f.label == null ? "" : f.label);
if (f.value instanceof Boolean)
{
w.setValue((Boolean) f.value);
}
rootWidget = w;
}
else if (f.value instanceof EnumCreator)
{
EnumRadioGroup.RadioStyles style = EnumRadioGroup.RadioStyles.RADIO_INLINE;
if (!f.inline)
{
style = EnumRadioGroup.RadioStyles.RADIO;
}
EnumRadioGroup<EnumCreator> w = new EnumRadioGroup<EnumCreator>(
"EnumRadioGroup-" + f.hashCode(),
(EnumCreator) f.value, style, true,
getTranslationMap());
w.setValue((EnumCreator) f.value, false);
rootWidget = w;
}
else
{
throw new RuntimeException("Type " + f.value.getClass() + " is not supported!");
}
if (rootWidget instanceof HasReadOnly)
{
((HasReadOnly) rootWidget).setReadOnly(f.readOnly);
}
if (!f.readOnly)
{
if (!f.allowBlank)
{
if (clazz == Integer.class)
{
((HasValidators) rootWidget).addValidator(
createBlankValidator("An integer value is expected"));
}
else if (rootWidget instanceof HasValidators)
{
((HasValidators) rootWidget).addValidator(createBlankValidator());
}
}
if (f.validators != null && rootWidget instanceof HasValidators)
{
for (Validator v : f.validators)
{
((HasValidators) rootWidget).addValidator(v);
}
}
}
widgets.put(f, rootWidget);
}
}
/**
* Layouts the input dialog.
*
* @param items
* The input dialog model.
*/
private void layout(Item[] items)
{
if (tabbed)
{
// build tabs
boolean first = true;
for (Item it : items)
{
Panel panel = (Panel) it;
AnchorListItem tab = new AnchorListItem();
tab.setActive(first);
if (first)
{
activePanel = panel;
first = false;
}
tab.setText(panel.label);
tabs.put(panel, tab);
navTabs.add(tab);
}
// build forms
first = true;
for (Item it : items)
{
Panel panel = (Panel) it;
Form form = new Form(FormType.HORIZONTAL);
forms.put(panel, form);
form.add(layoutPanel(form, panel, true));
form.setVisible(first);
if (first)
{
activeForm = form;
first = false;
}
formsPanel.add(form);
}
navTabs.setVisible(true);
}
else
{
Form form = new Form(FormType.HORIZONTAL);
forms.put(null, form);
form.add(layoutPanel(form, new Panel(null, labelSize, items), false));
formsPanel.add(form);
}
}
/**
* Layouts one panel, this is part of the main layout logic.
*
* @param f
* Form item.
* @param panel
* Panel item.
* @param nolabel
* Whether labels should be visible.
*
* @return The layout result in a form of UI widget.
*/
private FieldSet layoutPanel(Form f, Panel panel, boolean nolabel)
{
FieldSet fieldSet = new FieldSet();
if (panel.label != null && !nolabel)
{
fieldSet.add(new Legend(panel.label));
}
if (panel.subItems == null)
{
return fieldSet;
}
for (Item item : panel.subItems)
{
Span span = null;
if (item instanceof Span)
{
span = (Span) item;
}
else if (item instanceof Field)
{
span = new Span(item);
}
if (span != null && span.subItems != null && span.subItems.length > 0)
{
fieldSet.add(layoutSpan(span, panel.labelSize != null ? panel.labelSize : labelSize));
}
else if (item instanceof Panel)
{
Row row = new Row();
Column fullColl = new Column(ColumnSize.XS_12);
row.add(fullColl);
fullColl.add(fieldSet);
fieldSet.add(layoutPanel(f, (Panel) item, false));
}
}
return fieldSet;
}
/**
* Layouts one line of widgets.
*
* @param span
* The items on one line.
* @param labelSize
* Label size.
*
* @return The layout result in a form of UI widget.
*/
private Row layoutSpan(Span span, int labelSize)
{
if (span.subItems == null || span.subItems.length == 0)
{
return null;
}
ColumnSize[] colsizes = ColumnSize.values();
List<UIObject> uiObjects = new ArrayList<>();
List<UIObject> skip = new ArrayList<>();
UIObject firstLabel = null;
boolean first = true;
for (Item item : span.subItems)
{
if (item instanceof WidgetItem)
{
uiObjects.add(((WidgetItem) item).widget);
skip.add(((WidgetItem) item).widget);
}
else if (item instanceof Field)
{
Field f = (Field) item;
HasValue w = widgets.get(f);
IsWidget label = buildLabel(f, w);
if (first && label != null)
{
firstLabel = (UIObject) label;
}
else if (label != null)
{
uiObjects.add((UIObject) label);
}
Widget wRoot = buildValidatorUI(f, w);
uiObjects.add(wRoot);
if (f.labelSize != null && f.labelSize >= 0 && f.labelSize <= 12)
{
if (f.labelSize > 0)
{
((UIObject) label).addStyleName(colsizes[f.labelSize - 1].getCssName());
}
skip.add((UIObject) label);
if (first)
{
labelSize = f.labelSize;
}
}
if (f.fieldSize != null && f.fieldSize >= 0 && f.fieldSize <= 12)
{
if (f.fieldSize > 0)
{
wRoot.addStyleName(colsizes[f.fieldSize - 1].getCssName());
}
skip.add(wRoot);
}
first = false;
}
}
int colSize = (12 - labelSize) / uiObjects.size();
if (colSize < 1)
{
colSize = 1;
}
ColumnSize colsize = colsizes[colSize - 1];
int remain = 12 - (colSize*uiObjects.size() + labelSize);
Row row = new Row();
FormGroup fg = new FormGroup();
row.add(fg);
UIObject firstWidget = uiObjects.isEmpty() ? null : uiObjects.get(0);
if (firstLabel != null)
{
firstLabel.addStyleName(colsizes[labelSize - 1].getCssName());
fg.add((IsWidget) firstLabel);
}
else if (firstWidget != null && !skip.contains(firstWidget))
{
firstWidget.addStyleName(ColumnOffset.values()[labelSize].getCssName());
}
for (UIObject obj : uiObjects)
{
ColumnSize effectiveSize = colsize;
if (remain > 0)
{
effectiveSize = colsizes[colSize];
remain--;
}
if (!skip.contains(obj))
{
obj.addStyleName(effectiveSize.getCssName());
}
fg.add((IsWidget) obj);
}
return row;
}
/**
* Builds label widget.
*
* @param f
* Field for which to build the label.
* @param w
* The field widget.
*
* @return The built label widget.
*/
private Widget buildLabel(Field f, HasValue w)
{
FormLabel label = null;
if (!(w instanceof CheckBox))
{
if (f.label != null && !f.label.isEmpty())
{
label = new FormLabel();
label.setText(f.label + ":");
}
}
return label;
}
/**
* Assembles the field widget into a set of widgets capable to validate and show validation
* errors.
*
* @param f
* Field for which to build the validation UI.
* @param w
* The field widget.
*
* @return The built validation UI.
*/
private Widget buildValidatorUI(Field f, HasValue w)
{
FlowPanel widgetPanel = new FlowPanel();
if (f.inputAddon == null)
{
widgetPanel.add((IsWidget) w);
}
else
{
InputGroup iGroup = new InputGroup();
iGroup.add((IsWidget) w);
iGroup.add(f.inputAddon);
widgetPanel.add(iGroup);
}
if (f.tooltip != null && f.tooltip.trim().length() > 0)
{
Icon icon = new Icon(IconType.INFO_CIRCLE);
Tooltip tooltip = new Tooltip(icon, f.tooltip);
widgetPanel.add(tooltip);
}
InlineHelpBlock hlp = new InlineHelpBlock();
hlp.setIconType(IconType.EXCLAMATION_TRIANGLE);
widgetPanel.add(hlp);
return widgetPanel;
}
/**
* Creates a blank validator with the default message.
*
* @return a blank validator
*/
private Validator createBlankValidator()
{
return createBlankValidator(null);
}
/**
* Creates a blank validator with a custom message.
*
* @param errMessage
* The error message
*
* @return The blank validator
*/
private Validator createBlankValidator(String errMessage)
{
return new SensitiveBlankValidatorWrapper(errMessage == null ?
new BlankValidator() :
new BlankValidator(errMessage));
}
/**
* Checks whether the supplied input dialog model has any defined label.
*
* @param items
* The input dialog model.
*
* @return <code>true</code> when there is any label defined in the supplied model
*/
private boolean hasLabels(Item... items)
{
for (Item item : items)
{
if (item instanceof Field)
{
Field f = (Field) item;
if ((f.value == Boolean.class || f.value instanceof Boolean) && f.inline)
{
// inline check boxes do not have labels
continue;
}
String label = f.label;
if (label != null && !label.trim().isEmpty())
{
return true;
}
}
else if (item instanceof Group)
{
boolean res = hasLabels(((Group) item).subItems);
if (res)
{
return true;
}
}
}
return false;
}
/**
* The root interface of the input dialog model.
*/
public interface Item
{
}
/**
* The root interface for items having other sub items.
*/
public abstract static class Group
implements Item
{
/** sub items */
Item[] subItems;
}
/**
* Group of items spanning one line.
*/
public static class Span
extends Group
{
/**
* Ctor.
*
* @param items
* The items to be spanned on one line.
*/
public Span(Item... items)
{
subItems = items;
}
}
/**
* Group of items positioned on a single form panel.
*/
public static class Panel
extends Group
{
/** Optional panel label */
String label;
/** the column size of the label area dedicated to the sub items */
Integer labelSize;
/**
* Ctor.
*
* @param label
* The panel label.
* @param items
* The items to be displayed on one form panel.
*/
public Panel(String label, Item... items)
{
this.label = label;
subItems = items;
}
/**
* Ctor.
*
* @param label
* The panel label.
* @param labelSize
* Labels size.
* @param items
* The items to be displayed on one form panel.
*/
public Panel(String label, int labelSize, Item... items)
{
this.label = label;
subItems = items;
this.labelSize = labelSize;
}
}
/**
* A single input dialog field.
*
* @param <ValType>
* The value type the field represents.
*/
public static class Field<ValType>
implements Item
{
/** Optional field label */
String label = "";
/** Label size */
Integer labelSize;
/** Field size */
Integer fieldSize;
/** Tooltip */
String tooltip;
/** The value to be displayed */
ValType value;
/** Whether blank value is accepted */
boolean allowBlank = true;
/** Any custom validators */
Validator<ValType>[] validators = null;
/** List of values to be displayed in a suggestion list box */
List<String> suggestions;
/** Additional input widget */
AbstractInputGroupAddon inputAddon;
/** List of options for selection fields */
List<Option> options;
/** Header to be displayed in the selection list for selection fields */
String header;
/** Whether the field should be laid out as inline, relevant for check and radio boxes */
boolean inline = true;
/** Number of lines for text area field */
int lines = 0;
/** Read only flag */
boolean readOnly;
/** Password flag */
boolean password;
/**
* Default constructor.
*/
public Field()
{
}
/**
* Constructor.
*
* @param label
* Field label.
* @param value
* Initial field value.
*/
public Field(String label, ValType value)
{
this(label, value, false);
}
/**
* Constructor. The list of validators remains {@code null}.
*
* @param label
* Field label.
* @param value
* Initial field value.
* @param allowBlank
* Whether blank value is allowed.
*/
public Field(String label, ValType value, boolean allowBlank)
{
this.label = label;
this.value = value;
this.allowBlank = allowBlank;
}
/**
* Constructor.
*
* @param label
* Field label.
* @param value
* Initial field value.
* @param allowBlank
* Whether blank value is allowed.
* @param validators
* Custom validators.
*/
public Field(String label,
ValType value,
boolean allowBlank,
Validator<ValType>... validators)
{
this.label = label;
this.value = value;
this.allowBlank = allowBlank;
this.validators = validators;
}
/**
* Suggestions setter.
*
* @param suggestions
* List of suggestions.
* @return returns <code>this</code>
*/
public Field withSuggestions(List<String> suggestions)
{
this.suggestions = suggestions;
return this;
}
/**
* Tooltip setter.
*
* @param tooltip
* Field tooltip.
*
* @return returns <code>this</code>
*/
public Field withTooltip(String tooltip)
{
this.tooltip = tooltip;
return this;
}
/**
* Addon setter.
*
* @param inputAddon
* Field addon widget.
*
* @return returns <code>this</code>
*/
public Field withAddon(AbstractInputGroupAddon inputAddon)
{
this.inputAddon = inputAddon;
return this;
}
/**
* Inline setter.
*
* @param inline
* Inline flag.
*
* @return returns <code>this</code>
*/
public Field inline(boolean inline)
{
this.inline = inline;
return this;
}
/**
* Lines setter.
*
* @param lines
* Text area number of lines.
*
* @return returns <code>this</code>
*/
public Field lines(int lines)
{
this.lines = lines;
return this;
}
/**
* Readonly setter.
*
* @param readOnly
* Field read only flag.
*
* @return returns <code>this</code>
*/
public Field readOnly(boolean readOnly)
{
this.readOnly = readOnly;
return this;
}
/**
* Password flag setter.
*
* @param password
* Field password flag.
*
* @return returns <code>this</code>
*/
public Field password(boolean password)
{
this.password = password;
return this;
}
/**
* Label size setter.
*
* @param size
* Field label size.
*
* @return returns <code>this</code>
*/
public Field withLabelSize(int size)
{
this.labelSize = size;
return this;
}
/**
* Field size setter.
*
* @param size
* Field size.
*
* @return returns <code>this</code>
*/
public Field withFieldSize(int size)
{
this.fieldSize = size;
return this;
}
/**
* Options builder.
*
* @param optionsList
* List of options.
*
* @return list of {@link Option} instances
*/
protected List<Option> buildOptions(List<String> optionsList)
{
List<Option> optionList = new ArrayList<>(optionsList.size());
for (String option : optionsList)
{
Option opt = new Option();
opt.setValue(option);
opt.setText(option);
optionList.add(opt);
}
return optionList;
}
/**
* Options builder.
*
* @param optionsMap
* List of options map [value-display text].
*
* @return list of {@link Option} instances
*/
protected List<Option> buildOptions(Map<String, String> optionsMap)
{
List<Option> optionList = new ArrayList<>(optionsMap.size());
for (Map.Entry<String, String> option : optionsMap.entrySet())
{
Option opt = new Option();
opt.setValue(option.getKey());
opt.setText(option.getValue());
optionList.add(opt);
}
return optionList;
}
}
/**
* Select input field.
*/
public static class SelectField
extends Field<String>
{
/** Header to be displayed in the selection list box */
String header;
/**
* Default ctor.
*/
public SelectField()
{
super();
}
/**
* Ctor.
*
* @param label
* Field label.
* @param options
* Select field options.
* @param value
* The initial value.
*/
public SelectField(String label, List<String> options, String value)
{
super(label, value, false);
this.options = buildOptions(options);
}
/**
* Ctor.
*
* @param label
* Field label.
* @param options
* Select field options.
* @param value
* The initial value.
*/
public SelectField(String label, Map<String, String> options, String value)
{
super(label, value, false);
this.options = buildOptions(options);
}
/**
* Ctor.
*
* @param label
* Field label.
* @param options
* Select field options.
* @param value
* The initial value.
* @param header
* The select list box header.
*/
public SelectField(String label, List<Option> options, String value, String header)
{
this(label, options, value, header, false);
}
/**
* Ctor.
*
* @param label
* Field label.
* @param options
* Select field options.
* @param value
* The initial value.
* @param header
* The select list box header.
* @param allowBlank
* Allow blank flag.
*/
public SelectField(String label,
List<Option> options,
String value,
String header,
boolean allowBlank)
{
super(label, value, allowBlank);
this.header = header;
this.options = options;
}
}
/**
* Multi selection field.
*/
public static class MultiSelectField
extends Field<List<String>>
{
/**
* Default ctor.
*/
public MultiSelectField()
{
super();
}
/**
* Ctor.
*
* @param label
* Field label.
* @param options
* Select field options.
* @param value
* The initial value.
* @param allowBlank
* Allow blank flag.
*/
public MultiSelectField(String label,
List<String> options,
List<String> value,
boolean allowBlank)
{
super(label, value, allowBlank);
this.options = buildOptions(options);
}
/**
* Ctor.
*
* @param label
* Field label.
* @param options
* Select field options.
* @param value
* The initial value.
* @param allowBlank
* Allow blank flag.
*/
public MultiSelectField(String label,
Map<String, String> options,
List<String> value,
boolean allowBlank)
{
super(label, value, allowBlank);
this.options = buildOptions(options);
}
/**
* Ctor.
*
* @param label
* Field label.
* @param options
* Select field options.
* @param value
* The initial value.
* @param allowBlank
* Allow blank flag.
* @param header
* The select list box header.
*/
public MultiSelectField(String label,
List<Option> options,
List<String> value,
boolean allowBlank,
String header)
{
super(label, value, allowBlank);
this.options = options;
this.header = header;
}
}
/**
* Field item wrapping arbitrary widget.
*/
public static class WidgetItem
extends Field
{
/** The field widget */
UIObject widget;
/**
* Ctor.
*
* @param widget
* The field widget.
*/
public WidgetItem(UIObject widget)
{
this.widget = widget;
}
}
/**
* This class wraps any validator and relaxes its validation logic in such a way that
* it always considers the disabled editor valid.
*/
private static class SensitiveBlankValidatorWrapper
implements Validator
{
/** The wrapped validator */
private BlankValidator blankValidator;
/**
* Ctor.
*
* @param blankValidator
* The wrapped validator.
*/
public SensitiveBlankValidatorWrapper(BlankValidator blankValidator)
{
this.blankValidator = blankValidator;
}
/**
* Priority value for this validator. Lower the number, higher the priority.
*
* @return the priority.
*/
@Override
public int getPriority()
{
return blankValidator.getPriority();
}
/**
* Validate the field.
*
* @param editor the {@link Editor}.
* @param value the value
* @return the list
*/
@Override
public List<EditorError> validate(Editor editor, Object value)
{
if (editor instanceof HasEnabled && !((HasEnabled) editor).isEnabled())
{
return Collections.emptyList();
}
return blankValidator.validate(editor, value);
}
}
}