Documentation

Documentation versions (currently viewingVaadin 24)

Extending Components

Create a new component by extending any existing component.

You can create a new component by extending any existing component. For most components, there is a client-side component and a corresponding server-side component:

  • Client-side component: Contains the HTML, CSS, and JavaScript, and defines a set of properties that determine the component’s behavior on the client side.

  • Server-side component: Contains Java code that allows client-side properties to be changed, and manages the component’s behavior on the server side.

You can extend a component on either the server side or the client side. These are alternative approaches that are mutually exclusive.

This section demonstrates the two different approaches to achieving the same changes to the prebuilt text field component.

Extending a Component Using the Server-side Approach

Extending a server-side component is useful when you want to add new functionality (as opposed to visual aspects) to an existing component. You might use this approach, for example, when automatically processing data, when adding default validators, or when combining multiple simple components into a field that manages complex data.

Tip
Consider using a Web Component
If your component contains a lot of logic that could conveniently be done on the client side, consider implementing it as a Web Component and creating a wrapper for it. This approach may offer a better user experience and result in less load on the server.

This example creates a NumericField component by extending the TextField component. The new component contains a default number that the user can change using + and - controls.

Number Field server side extension

Example: Creating a NumericField component by extending the TextField component.

@CssImport("./styles/numeric-field-styles.css")
public class NumericField extends TextField {

    private Button substractBtn;
    private Button addBtn;

    private static final int DEFAULT_VALUE = 0;
    private static final int DEFAULT_INCREMENT = 1;

    private int numericValue;
    private int incrementValue;
    private int decrementValue;

    public NumericField() {
        this(DEFAULT_VALUE, DEFAULT_INCREMENT,
                -DEFAULT_INCREMENT);
    }

    public NumericField(int value, int incrementValue,
                        int decrementValue) {
        setNumericValue(value);
        this.incrementValue = incrementValue;
        this.decrementValue = decrementValue;

        setPattern("-?[0-9]*");
        setPreventInvalidInput(true);

        addChangeListener(event -> {
            String text = event.getSource().getValue();
            if (StringUtils.isNumeric(text)) {
                setNumericValue(Integer.parseInt(text));
            } else {
                setNumericValue(DEFAULT_VALUE);
            }
        });

        substractBtn = new Button("-", event -> {
            setNumericValue(numericValue +
                    decrementValue);
        });

        addBtn = new Button("+", event -> {
            setNumericValue(numericValue +
                    incrementValue);
        });

        getElement().setAttribute("theme", "numeric");
        styleBtns();

        addToPrefix(substractBtn);
        addToSuffix(addBtn);
    }

    private void styleBtns() {
        // Note: The same as addThemeVariants
        substractBtn.getElement()
                .setAttribute("theme", "icon");
        addBtn.getElement()
                .setAttribute("theme", "icon");
    }

    public void setNumericValue(int value) {
        numericValue = value;
        setValue(value + "");
    }

    // getters and setters
}

As an alternative, you can extend the Composite class, which has a minimal API. This hides methods available in the more extensive API that’s exposed when your custom component extends an implementation of Component.

Note
Accessing, updating and querying elements
The Element API contains methods to update and query various parts of the element, such as the attributes. Every component has a getElement() method that allows you to access it. See Creating a Component Using Multiple Elements for more.

Import additional styles for the component using the @CssImport annotation. These styles apply only to the NumericField component, and not to all TextField components.

Example: Creating numeric-field-styles.css to customize the appearance of the vaadin-text-field component.

:host([theme~="numeric"]) [part="input-field"] {
    background-color: var(--lumo-base-color);
    border: 1px solid var(--lumo-contrast-30pct);
    box-sizing: border-box;
}

:host([theme~="numeric"]) [part="value"]{
    text-align: center;
}

See Styling Vaadin Components for more information.

Extending a Component Using the Client-side Approach

Vaadin client-side components are based on Polymer 3, which supports extending existing components. You can use the extends property to extend existing Polymer elements.

A template can be inherited from another Polymer element in five ways:

  1. Inheriting a base class template without modifying it.

  2. Overriding a base class template in a child class.

  3. Modifying a copy of a superclass template.

  4. Extending a base class template in a child class.

  5. Providing template-extension points in a base class for content from a child class.

Extending by Modifying a Copy of a Superclass Template

This example demonstrates how to create a new component by modifying a copy of a superclass template. You build a NumberField by extending TextField. The new component contains a default number that the user can change using + and - controls.

Number Field client side extension

It’s important to remember that when a component template is extended, the properties and methods of the parent template become available to the child template.

Note
A child component uses the parent component’s template
By default, a child component uses the template of the parent component, unless the child component provides its own template by overriding the static getter method template(). The parent’s template is accessed using super.template().

Next, specify the element from which the child component inherits. In this case, specify that NumberField inherits from TextField, including its properties and methods:

import { html } from
   '@polymer/polymer/lib/utils/html-tag.js';
import { TextField } from '@vaadin/text-field';

let memoizedTemplate;

class NumberField extends TextField {

    static get template() {
        if (!memoizedTemplate) {
            const superTemplate = super.template
                    .cloneNode(true);
            const inputField = superTemplate.content
                .querySelector('[part="input-field"]');
            const prefixSlot = superTemplate.content
            .querySelector('[name="prefix"]');
            const decreaseButton = html`<div
                part="decrease-button"
                on-click="_decreaseValue"></div>`;
            const increaseButton = html`<div
                part="increase-button"
                on-click="_increaseValue"></div>`;
            inputField.insertBefore(
                decreaseButton.content, prefixSlot);
            inputField.appendChild(
                increaseButton.content);
            memoizedTemplate = html`<style>
                 [part="decrease-button"]::before {
                   content: "−";
                 }

                 [part="increase-button"]::before {
                   content: "+";
                 }
               </style>
               ${superTemplate}`;
        }
        return memoizedTemplate;
    }

    static get is() {
        return 'vaadin-number-field';
    }

    static get properties() {
        return {
            decrementValue: {
              type: Number,
              value: -1,
              reflectToAttribute: true,
              observer: '_decrementChanged'
            },
            incrementValue: {
              type: Number,
              value: 1,
              reflectToAttribute: true,
              observer: '_incrementChanged'
            }

            // Note: the value is stored in the
            // TF's value property.
        };
    }

    _decreaseValue() {
        this.__add(this.decrementValue);
    }

    _increaseValue() {
        this.__add(this.incrementValue);
    }

    __add(value) {
        this.value = parseInt(this.value, 10) + value;
        this.dispatchEvent(
            new CustomEvent('change', {bubbles: true}));
    }

    _valueChanged(newVal, oldVal) {
        this.value = this.focusElement.value;
        super._valueChanged(this.value, oldVal);
    }

    /* ... */
}

To modify the template, override the template() static getter. The expression ${super.template} inserts the base class template into the newly constructed template. The newly constructed template is memoized for further invocations of template().

See Inherit a template from another Polymer element in the Polymer documentation for more.

42913850-FAD3-4184-9B69-02F29B1A14CE