Handling User Events in a PolymerTemplate

Client Side Event Handlers

PolymerTemplate defines a special syntax on-event="methodName" for attaching client side event handlers to elements. (Note! that the method should be without any arguments and have no parenthesis)

To wire an event to a Polymer.Element method you can write a template as:

<dom-module id="x-custom">
  <template>
    <button on-click="handleClick">Say hello</button>
  </template>
  <script>
    class XCustom extends Polymer.Element {

      static get is() {return 'x-custom'}

      handleClick() {
        console.log('Button was clicked.');
        window.alert('Hello');
      }
    }
    customElements.define(XCustom.is, XCustom);
  </script>
</dom-module>

Clicking on the <button> will now show an alert in the browser. You can listen to any event using the on-event syntax, it does not matter if it is a built-in browser event or a custom event from e.g. a web component.

Server-Side Event Handlers

To handle a DOM event in a template on the server side you can create a method with the event name and annotate it with @EventHandler.

So to listen to the handleClick event on the server you could have the template as:

<dom-module id="event-handler">
  <template>
    <button on-click="handleClick">Click me</button>
  </template>
  <script>
    class EventHandler extends Polymer.Element {
      static get is() { return 'event-handler' }
    }
    customElements.define(EventHandler.is, EventHandler);
  </script>
</dom-module>

And define the server class as:

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

    @EventHandler
    private void handleClick() {
        System.out.println("Received a handle click event");
    }
}

The framework will wire up the client-side event when having the @EventHandler annotation on the method handleClick().

Note
If a case arises where there is a client-side implementation of the server event handler the client side method will be executed before the server-side event handler method is called.

Adding Event Data to server-side event

An event can also include additional information about what has happened, e.g. which mouse button was used for a click event. When you use @EventHandler annotation, all constructor parameters should have an @EventData annotation that tells the framework what data to send from the browser.

<!-- same template as for the server-side event handler -->
  <template>
    <button on-click="handleClick">Click me</button>
  </template>

To get some extra data on event type and element tag name the server class definition could be built like:

@Tag("event-handler")
@HtmlImport("/com/example/EventHandler.html")
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 +"]");
    }
}

Now the client would send the extra information back to the server for event.type, event.srcElement.tagName and the event.offset[X/Y] can then be used like normal variables.

Note
The server will throw an exception if the EventData can not be converted to given format. for instance you could get the exception java.lang.ClassCastException: Cannot cast elemental.json.impl.JreJsonNumber to elemental.json.JsonObject Also the client might throw exceptions if the value given for EventData can not be executed or converted to Json.

There is a shorthand for getting model specific item as an object in your event handler. To be able to use it you should define your model (see Using Beans with a PolymerTemplate Model) in the template class.

@Tag("model-item-handler")
@HtmlImport("/com/example/ModelItemHandler.html")
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());
    }
}

Now you can use the template repeater (dom-repeat) (see Using List of Items in a PolymerTemplate with template repeater) and handle click events on the server side with Message as the parameter type.

<dom-module id="model-item-handler">
    <template>
    <dom-repeat items="[[messages]]">
      <template><div class='msg' on-click="handleClick">[[item.text]]</div></template>
    </dom-repeat>
  </template>
  <script>
    class ModelItemHandler extends Polymer.Element {
      static get is() { return 'model-item-handler' }
    }
    customElements.define(ModelItemHandler.is, ModelItemHandler);
  </script>
</dom-module>

The method handleClick will be called in the server side with the data identified by event.model.item once the item is clicked.

Note
You can use the annotation @ModelItem with any value provided as a data path. By default the data path is event.model.item. But your data type should be declared somehow via the model definition (it should be referenced from the model).
Note
Please note that @ModelItem is just a convenience way of model data access. The argument which you receive in your event handler callback is the model data from the server side which you may access directly via your model instance. It means that the server doesn’t update the model item anyhow from the client. So 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 will be completely ignored by the server-side and the current model data will be used instead. You always should keep your model in sync on the server and client-sides, by correctly updating it.

So if you have the following model definition and the event handler method:

    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());
    }

Then the client side code below won’t update the name of the UserInfo bean instance.

<dom-module id="contact-handler">
    <template>
        <input id="name" type="text">
        <button on-click="onClick">Send the contact</button>
    </template>
</dom-module>

<script>
class ContactHandler extends Polymer.Element {
      static get is() { return 'contact-handler' }

        onClick(event) {
            this.userInfo.name = this.$.name.value;
            event.detail = {
                userInfo: this.userInfo,
        };
    }
    customElements.define(ContactHandler.is, ContactHandler);
</script>
}

In this example the server-side model becomes desynchronized with the client side because client side model is updated incorrectly. The line this.userInfo.name = this.$.name.value should be replaced to this.set("userInfo.name", this.$.name.value). That’s the correct way to update sub-properties in Polymer. But in this case the server-side model will be updated automatically for you and there is no need to send this custom event at all. You may just notify somehow the server about the click event (e.g. via this.$server and a @ClientCallable method, see Creating A Simple Component Using the Template API) and get the model value directly from the server-side model.