Event binding
In JOPS, an event handler is a method of the view class. You connect the two in the layout: mark the element with jops-event-bind and give an on* attribute the name of the method.
Binding an event
Section titled “Binding an event”<form jops-event-bind onsubmit="onSave"> <input name="name" jops-event-bind oninput="onNameInput"> <button type="submit">Save</button></form>import { View } from "jops-core";
export default class Profile extends View { onNameInput(event) { this.name = event.target.value; }
onSave(event) { event.preventDefault(); const data = new FormData(event.target); // ...send data }}- Any
on*attribute works:onclick,onsubmit,oninput,onchange,onkeydown, and so on. - The handler receives the native DOM event, and
thisis the view. - The value is a method name, never code. Write
onclick="onSave", notonclick="onSave()".
Binding is done by bindEvents(), which the framework calls when the view is shown, after onResume() in the lifecycle. It covers the markup of the view’s layout, across all of its top-level elements.
To pass data to a handler, keep it on the view instance (this.task) rather than in the markup. A list item that is its own view knows its own record:
import { View, layout } from "jops-core";
export default class TaskItem extends View { constructor(task) { super(); this.task = task; this.layout = layout` <li class="item"> <span>${task.title}</span> <button jops-event-bind onclick="onDelete">Delete</button> </li>`; this.inflate(); }
onDelete() { Tasks.remove(this.task.id); }}Generated markup is not bound
Section titled “Generated markup is not bound”bindEvents() only wires the layout. HTML you generate later, with Element.set() or innerHTML, is not bound. Worse, the browser treats onclick="onDelete" in that HTML as an inline script and throws a ReferenceError (or a Content-Security-Policy violation).
For generated markup you have two good options:
- Make each item a subview, as in the
TaskItemexample above. - Use event delegation: one bound listener on a container from the layout, with
data-*attributes on the generated elements.
Event delegation
Section titled “Event delegation”<table> <tbody id="lines" jops-event-bind onclick="onLineAction"></tbody></table>import { View, layout } from "jops-core";
export default class Lines extends View { showLines(lines) { this.get("lines").set( lines .map((line, i) => layout` <tr> <td>${line.description}</td> <td><button data-action="remove" data-index="${i}">✕</button></td> </tr>`) .join(""), ); }
onLineAction(event) { const button = event.target.closest("[data-action]"); if (button?.dataset.action === "remove") { this.removeLine(Number(button.dataset.index)); } }}The <tbody> is part of the layout, so it is bound once. Rows can be replaced as often as you like, and clicks on them still reach onLineAction().
For anything else, addEventListener() in onLayout() is fine.