Extending Components
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.
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:
-
Inheriting a base class template without modifying it.
-
Overriding a base class template in a child class.
-
Modifying a copy of a superclass template.
-
Extending a base class template in a child class.
-
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.
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