Binding Events on Class Name - ldco2016/microurb_web_framework GitHub Wiki
import { User } from "../models/User";
export class UserForm {
constructor(public parent: Element, public model: User) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<div>User name: ${this.model.get("name")}</div>
<div>User age: ${this.model.get("age")}</div>
<input />
<button>Click Me</button>
</div>`;
}
I now have the ability to print up information from a model and show it inside my template, but now I need to figure out how to do the opposite of that. I want to ensure I can take some user input like a button click or user typing and use that information to somehow update my model.
I will set my sites low by or something easy to get started. I want to add a button to my template called Set Random Age and anytime I click on that button, I want to try to set a random age on my user model and of course, once I set the random age, I would also expect the HTML on the screen to be automatically updated as well.

The first thing I will do is add in a new button to my template:
import { User } from "../models/User";
export class UserForm {
constructor(public parent: Element, public model: User) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<div>User name: ${this.model.get("name")}</div>
<div>User age: ${this.model.get("age")}</div>
<input />
<button>Click Me</button>
<button>Set Random Age</button>
</div>`;
}
bindEvents(fragment: DocumentFragment): void {
const eventsMap = this.eventsMap();
for (let eventKey in eventsMap) {
const [eventName, selector] = eventKey.split(":");
fragment.querySelectorAll(selector).forEach((element) => {
element.addEventListener(eventName, eventsMap[eventKey]);
});
}
}
render(): void {
const templateElement = document.createElement("template");
templateElement.innerHTML = this.template();
this.bindEvents(templateElement.content);
this.parent.append(templateElement.content);
}
}
Now I can go back up to my event handler and add in an event handler for anytime my button gets clicked on, but I already have a click event handler that will bind to all the different buttons inside my template and that's an issue. If I always bind to events based upon the tag name, I might accidentally end up binding an event handler to multiple different elements. So I need to figure out which button to bind to and for that all I have to do is add a class name or id to any elements in my template like so:
import { User } from "../models/User";
export class UserForm {
constructor(public parent: Element, public model: User) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
"click:.set-age": this.onSetAgeClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<div>User name: ${this.model.get("name")}</div>
<div>User age: ${this.model.get("age")}</div>
<input />
<button>Click Me</button>
<button class="set-age">Set Random Age</button>
</div>`;
}
The .
means try to find an element inside my template with a class name of set-age
. This will work naturally out of the box and the reason for that is that when I put together my bindEvents()
method, I am trying to find an element inside of my template using querySelectorAll()
and so effectively what I am passing into the selector is the equivalent to .set-age
.
The one downside is the event handler of this.onButtonClick
is still going to bind to both buttons. So I will delete that and define onSetAgeClick
instead like so:
eventsMap(): { [key: string]: () => void } {
return {
"click:.set-age": this.onSetAgeClick,
};
}
onSetAgeClick(): void {
console.log("button was clicked");
}
template(): string {
return `<div>
<h1>User Form</h1>
<div>User name: ${this.model.get("name")}</div>
<div>User age: ${this.model.get("age")}</div>
<input />
<button>Click Me</button>
<button class="set-age">Set Random Age</button>
</div>`;
}
So now whenever I render this to the browser, this class will try to find some element with a class name of set-age
, it will watch for a click
event and anytime someone clicks on that it will run this.onSetAgeClick
.