Handling User Events in Polymer Templates

Note
Use Lit templates instead
Lit templates are recommended. Polymer templates are available in the next long term supported Vaadin version (LTS), but they are deprecated.

Defining Client-side Event Handlers

PolymerTemplate defines special syntax to attach client-side event handlers to elements. Use the on-eventname="methodName" syntax to define the handler. Note that the method name should not have any arguments or parenthesis.

Example: Defining an event handler in the EventhandlerDemo JavaScript Polymer template.

class EventhandlerDemo extends PolymerElement {
    static get template() {
        return html`<button on-click="handleClick">Say hello</button>`;
    }

    static get is() {return 'eventhandler-demo'}

    handleClick() {
      console.log('Button was clicked.');
      window.alert('Hello');
    }
}
customElements.define(XCustom.is, XCustom);
  • This defines a <button> click that displays an alert in the browser.

You can use the on-eventname syntax to listen to both built-in browser events and custom events, for example from a Web Component. You can typically find the available events in the component documentation.

Handling Events on the Server side

You can use the @EventHandler annotation to handle DOM events defined in a Polymer template.

Example: Defining the handleClick method in a JavaScript Polymer template.

class EventHandler extends PolymerElement {
    static get template() {
        return html`<button on-click="handleClick">Click me</button>`;
    }
    static get is() { return 'event-handler' }
}
customElements.define(EventHandler.is, EventHandler);

Example: Using the @EventHandler annotation to reference the handleClick method.

@Tag("event-handler")
@JsModule("./com/example/event-handler.js")
public class EventHandlerPolymerTemplate extends PolymerTemplate<TemplateModel> {

    @EventHandler
    private void handleClick() {
        System.out.println("Received a handle click event");
    }
}
  • When you add the @EventHandler annotation on the handleClick() method, Vaadin automatically connects the handler to the client-side event .

Note
If you add both client and server-side event listeners, the client-side handlers are called before the server-side handlers.

Adding Event Data to Server-side Events

An event can include additional information about what occurred, for example the mouse button used for a click event. When you use the @EventHandler annotation, you can define an @EventData annotation for each method parameter. This annotation tells Vaadin which data to send from the browser. Any serializeable value present in the client-side event object can be sent. The available data depends on the specific event type.

Example: Defining the handleClick method in a JavaScript Polymer template.

// same template as for the server-side event handler
static get template() {
    return html`<button on-click="handleClick">Click me</button>`;
}

Example: Using the @EventData annotation to get get additional data in the Java template class.

@Tag("event-handler")
@JsModule("./com/example/event-handler.js")
public class EventDataHandlerPolymerTemplate extends PolymerTemplate<TemplateModel> {

    @EventHandler
    private void handleClick(@EventData("event.altKey") boolean altPressed,
            @EventData("event.srcElement.tagName") String tag,
            @EventData("event.offsetX") int offsetX,
            @EventData("event.offsetY") int offsetY) {
        System.out.println("Event alt pressed: " + altPressed);
        System.out.println("Event tag: " + tag.toLowerCase(Locale.ENGLISH));
        System.out.println("Click position on element: [" + offsetX + ", "+ offsetY +"]");
    }
}
  • The client parses the event data and sends the additional information back to the server for event.type, event.srcElement.tagName and event.offset[X/Y].

Note
If the EventData cannot be converted to the given format, the server could throw an exception, for example java.lang.ClassCastException: Cannot cast elemental.json.impl.JreJsonNumber to elemental.json.JsonObject. The client could also throw exceptions if the value given for EventData cannot be executed or converted to JSON.

Using the Template Repeater to Get Model-specific Event Data

You can use the <dom-repeat> helper element to get model-specific items as objects in your event handler, provided you use beans to define your template model class. See Using Beans with a PolymerTemplate Model) for more.

Example: Creating the ModelItemHandlerPolymerTemplate template and model class.

@Tag("model-item-handler")
@JsModule("./com/example/model-item-handler.js")
public class ModelItemHandlerPolymerTemplate
            extends PolymerTemplate<MessagesModel> {

    public static class Message {
        private String text;

        public Message() {
        }

        public Message(String text) {
            this.text = text;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }
    }

    public interface MessagesModel extends TemplateModel {
        void setMessages(List<Message> messages);
    }

    @EventHandler
    private void handleClick(@ModelItem Message message) {
        System.out.println("Received a message: " + message.getText());
    }
}
  • You can now handle click events on the server with the Message parameter type.

Example: Using the <dom-repeat> (template repeater) in a JavaScript Polymer template.

class ModelItemHandler extends PolymerElement {
    static get template() {
        return html`
            <dom-repeat items="[[messages]]">
                <template><div class='msg' on-click="handleClick">[[item.text]]</div></template>
            </dom-repeat>`;
    }
    static get is() { return 'model-item-handler' }
}
customElements.define(ModelItemHandler.is, ModelItemHandler);
  • When the item is clicked, the handleClick method is called on the server side and the data is identified by event.model.item.

Note
You can use the @ModelItem annotation with any value provided as a data path. By default, the data path is event.model.item, but you should declare your data type in some manmer via the model definition, so that it can be referenced from the model.

Modifying Model Items Before Events

The @ModelItem annotation is only a convenience way of accessing model data. The argument you receive in your event handler callback is the model data from the server side that you can access directly via your model instance. This means that the server does not update the model item on the client in any way. Therefore, if you create a custom event on the client side with data that you want to send to the server as a model item, it is ignored completely on the server side and the current model data is used instead. You should always keep your model in sync on the server and client, by updating it correctly.

To clearly demonstrate the point, the following example demonstrates the incorrect way to update the model.

Example: UserInfo model and event handler definition.

    public static class UserInfo {
        private String name;

        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }

    public interface Model extends TemplateModel {
        void setUserInfo(UserInfo userInfo);
    }

    @EventHandler
    private void onClick(
            @ModelItem("event.detail.userInfo") UserInfo userInfo) {
        System.err.println("contact : name = " + userInfo.getName());
    }

Example: JavaScript Polymer template that DOES NOT update the name of the UserInfo bean instance.

class ContactHandler extends PolymerElement {

    static get template() {
        return html`
            <input id="name" type="text">
            <button on-click="onClick">Send the contact</button>`;
    }

    static get is() { return 'contact-handler' }

    onClick(event) {
        this.userInfo.name = this.$.name.value;
        event.detail = {
            userInfo: this.userInfo,
        };
    }
}
customElements.define(ContactHandler.is, ContactHandler);
  • This example results in the server-side model and the client being out of sync because client-side model is not updated correctly.

  • To correctly update sub properties in Polymer, replace this.userInfo.name = this.$.name.value with this.set("userInfo.name", this.$.name.value).

  • However, in this case the server-side model is updated automatically for you and there is no need to send this custom event at all. You can simply notify the server in some manner about the click event, for example via this.$server and a @ClientCallable method. See Creating a Simple Component Using the PolymerTemplate API) for how to get the model value directly from the server-side model.

Additional Ways to Call the Server

There are two additional ways to call the server:

  • Use the @ClientCallable annotation: This annotation allows a Java method to be called from client-side code, using the this.$server.serverMethodName(args) notation. You can use this anywhere in your client-side Polymer class implementation. You can pass your own arguments to the method, as long as the types match the server-side method declaration. See @ClientCallable Annotation for more.

  • You can also define PropertyChangeListeners when working with templates. See Enabling Property Changes for more.

Receiving Events After Server Update

In some cases, you may want to execute client-side logic after a component is updated from the server (during a round trip). For example, the component constructor is called on the client side, but it is too early to do anything with the component at this stage, because the component does not yet have data from the server side. In these circumstances, you can use the afterServerUpdate method. When this method is defined for the component, it is called each time the component is updated from the server side, allowing you to configure the component with all available data.

Example: Using the afterServerUpdate method in a JavaScript Polymer template.

import {PolymerElement,html} from '@polymer/polymer/polymer-element.js';

class MyComponent extends PolymerElement {

    static get template() {
        return html`
            <div>
                <div>[[text]]</div>
            </div>`;
    }

    static get is() {
          return 'my-component';
    }

    afterServerUpdate(){
        console.log("The new 'text' value is: "+this.text);
    }
}

customElements.define(MyComponent.is, MyComponent);