Custom Field

Custom Field is a component for wrapping multiple components as a single field. It has the same features as Input Fields, such as its own label, helper, validation, and data binding. Use it to create custom input components.

Open in a
new tab
public class DateRangePicker extends CustomField<LocalDateRange> {

  private DatePicker start;
  private DatePicker end;

  public DateRangePicker(String label) {
    this();
    setLabel(label);
  }

  public DateRangePicker() {
    start = new DatePicker();
    start.setPlaceholder("Start date");

    end = new DatePicker();
    end.setPlaceholder("End date");

    // aria-label for screen readers
    start.getElement()
      .executeJs("const start = this.shadowRoot.querySelector('[part=\"text-field\"]').shadowRoot.querySelector('[part=\"value\"]');" +
        "start.setAttribute('aria-label', 'Start date');" +
        "start.removeAttribute('aria-labelledby');"
      );
    end.getElement()
      .executeJs("const end = this.shadowRoot.querySelector('[part=\"text-field\"]').shadowRoot.querySelector('[part=\"value\"]');" +
        "end.setAttribute('aria-label', 'End date');" +
        "end.removeAttribute('aria-labelledby');"
      );

    add(start, new Text(" – "), end);
  }

  @Override
  protected LocalDateRange generateModelValue() {
    return new LocalDateRange(start.getValue(), end.getValue());
  }

  @Override
  protected void setPresentationValue(LocalDateRange dateRange) {
    start.setValue(dateRange.getStartDate());
    end.setValue(dateRange.getEndDate());
  }
}

Basic Usage

Custom Field is optimised for wrapping the following components:

It can also be used to provide a label, helper, and the other field features, for components that don’t have them built-in, such as List Box.

Native Input Fields

Custom Field works with native HTML elements.

Open in a
new tab
public class PaymentInformationField extends CustomField<PaymentInformation> {

  private Input cardholderName;
  private Input cardNumber;
  private Input securityCode;

  public PaymentInformationField(String label) {
    this();
    setLabel(label);
  }

  public PaymentInformationField() {
    cardholderName = createInput("Cardholder name", "[\\p{L} \\-]+");
    cardNumber = createInput("Card number", "[\\d ]{12,23}");
    securityCode = createInput("Security code", "[0-9]{3,4}");

    HorizontalLayout layout = new HorizontalLayout(cardholderName, cardNumber, securityCode);
    // Removes default spacing
    layout.setSpacing(false);
    // Adds small amount of space between the components
    layout.getThemeList().add("spacing-s");

    add(layout);
  }

  private Input createInput(String label, String pattern) {
    Input input = new Input();
    input.getElement().setAttribute("aria-label", label);
    input.getElement().setAttribute("pattern", pattern);
    input.getElement().setAttribute("required", true);
    input.setPlaceholder(label);
    input.setType("text");
    return input;
  }

  @Override
  protected PaymentInformation generateModelValue() {
    return new PaymentInformation(
      cardholderName.getValue(),
      cardNumber.getValue(),
      securityCode.getValue()
    );
  }

  @Override
  protected void setPresentationValue(PaymentInformation paymentInformation) {
    cardholderName.setValue(paymentInformation.getCardholderName());
    cardNumber.setValue(paymentInformation.getCardNumber());
    securityCode.setValue(paymentInformation.getSecurityCode());
  }
}

Supported Features

Size Variants

The small theme variant can be used to make Custom Field’s label, helper, and error message smaller. Custom Field does not propagate its theme variant to its internal components, meaning each internal component’s theme variant must be set individually.

Open in a
new tab
public class MoneyField extends CustomField<Money> {

  private TextField amount;
  private Select<String> currency;

  public MoneyField(String label) {
    this();
    setLabel(label);
  }

  public MoneyField() {
    amount = new TextField();

    currency = new Select();
    currency.setItems("AUD", "CAD", "CHF", "EUR", "GBP", "JPY", "USD");
    currency.setWidth("6em");

    // aria-label for screen readers
    amount.getElement()
      .executeJs("const amount = this.shadowRoot.querySelector('[part=\"value\"]');" +
        "amount.setAttribute('aria-label', 'Amount');" +
        "amount.removeAttribute('aria-labelledby');");
    currency.getElement()
      .executeJs("const currency = this.shadowRoot.querySelector('vaadin-select-text-field').shadowRoot.querySelector('[part=\"input-field\"]');" +
        "currency.setAttribute('aria-label', 'Currency');" +
        "currency.removeAttribute('aria-labelledby');");

    HorizontalLayout layout = new HorizontalLayout(amount, currency);
    // Removes default spacing
    layout.setSpacing(false);
    // Adds small amount of space between the components
    layout.getThemeList().add("spacing-s");

    add(layout);
  }

  public void addThemeVariant(String theme) {
    getElement().getThemeList().add(theme);
    amount.addThemeName(theme);
    currency.getElement().getThemeList().add(theme);
  }

  @Override
  protected Money generateModelValue() {
    return new Money(amount.getValue(), currency.getValue());
  }

  @Override
  protected void setPresentationValue(Money money) {
    amount.setValue(money.getAmount());
    currency.setValue(money.getCurrency());
  }
}