Binding Events Handlers - ldco2016/microurb_web_framework GitHub Wiki
export class UserForm {
constructor(public parent: Element) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<input />
<button>Click Me</button>
</div>`;
}
render(): void {
const templateElement = document.createElement("template");
templateElement.innerHTML = this.template();
console.log(this.parent);
this.parent.append(templateElement.content);
}
}
I am going to go down to my render method and add additional logic that is going to call that eventsMap
method, take the object I get back, iterate over all the key/value pairs inside there and set up all these different event handlers.

I am not going to set up all this logic inside the render()
method, I think it would be really nice to set up a helper method so I don't clutter up the render()
method in general.
The Template Element I am making use of has a content
property inside of render right here: this.parent.append(templateElement.content);
. So the content
property is the actual reference to the HTML inside that Template Element.

The content
property is of type DocumentFragment. That is an object that is included inside the browser by default, a DocumentFragment is essentially an object that can contain a reference to some HTML. A DocumentFragment's purpose is to kind of hold some HTML inside of memory before it gets attached to the DOM. So essentially you are supposed to use a DocumentFragment for what I am using it right now, which is prepping some HTML to be inserted into the DOM.
So I will make a helper method that takes in a reference to a DocumentFragment and then iterate through the eventsMap
and try to bind all the events to the HTML inside that DocumentFragment. So I am going to create a helper method called bindEvents
like so:
export class UserForm {
constructor(public parent: Element) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<input />
<button>Click Me</button>
</div>`;
}
bindEvents(fragment: DocumentFragment): void {}
render(): void {
const templateElement = document.createElement("template");
templateElement.innerHTML = this.template();
console.log(this.parent);
this.parent.append(templateElement.content);
}
}
So the first thing I need to do is get a reference to my eventsMap
, so I can iterate over its object and set up all these different event handlers like so:
export class UserForm {
constructor(public parent: Element) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<input />
<button>Click Me</button>
</div>`;
}
bindEvents(fragment: DocumentFragment): void {
const eventsMap = this.eventsMap();
}
render(): void {
const templateElement = document.createElement("template");
templateElement.innerHTML = this.template();
console.log(this.parent);
this.parent.append(templateElement.content);
}
}
Now I can set up a for...in
loop to iterate over all the key/value pairs inside there like so:
export class UserForm {
constructor(public parent: Element) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<input />
<button>Click Me</button>
</div>`;
}
bindEvents(fragment: DocumentFragment): void {
const eventsMap = this.eventsMap();
for (let eventKey in eventsMap) {
}
}
render(): void {
const templateElement = document.createElement("template");
templateElement.innerHTML = this.template();
console.log(this.parent);
this.parent.append(templateElement.content);
}
}
So, eventKey
is going to be the strings of 'click:button'
and whatever else.
So I need to split the eventKey
in half, based on that colon to get the event name and the selector for the element I want to attach my event handler to like so:
export class UserForm {
constructor(public parent: Element) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<input />
<button>Click Me</button>
</div>`;
}
bindEvents(fragment: DocumentFragment): void {
const eventsMap = this.eventsMap();
for (let eventKey in eventsMap) {
const [eventName, selector] = eventKey.split(":");
}
}
render(): void {
const templateElement = document.createElement("template");
templateElement.innerHTML = this.template();
console.log(this.parent);
this.parent.append(templateElement.content);
}
}
Now this is some ES2015 syntax here, when I call eventKey
I expect to get back an array with two elements inside it. So I am using destructuring to get a reference to the first element inside there, eventName
and a reference to the second element inside there I am calling selector
.
In this case if I did a console log of eventName
I would expect back // click
and if I did a console log of selector
I would expect to get back // button
.
So now I can set up the actual event handler by looking at the fragment I passed in which is a reference to the HTML I am about to inject into the DOM and I am looking to find every element inside there that matches the selector
like so:
export class UserForm {
constructor(public parent: Element) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<input />
<button>Click Me</button>
</div>`;
}
bindEvents(fragment: DocumentFragment): void {
const eventsMap = this.eventsMap();
for (let eventKey in eventsMap) {
const [eventName, selector] = eventKey.split(":");
fragment.querySelectorAll(selector);
}
}
render(): void {
const templateElement = document.createElement("template");
templateElement.innerHTML = this.template();
console.log(this.parent);
this.parent.append(templateElement.content);
}
}
That will give me back an array of elements that match that selector
.
I can then iterate over that array and for every element that I match I can attach whatever event handler I had referenced this.onButtonClick
like so:
export class UserForm {
constructor(public parent: Element) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<input />
<button>Click Me</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, )
});
}
}
render(): void {
const templateElement = document.createElement("template");
templateElement.innerHTML = this.template();
console.log(this.parent);
this.parent.append(templateElement.content);
}
}
I am watching for the eventName
which in this case its simply click
and for the second argument to that addEventListener()
, I specify the callback function which is the value at eventsMap
like so:
export class UserForm {
constructor(public parent: Element) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<input />
<button>Click Me</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();
console.log(this.parent);
this.parent.append(templateElement.content);
}
}
I can now call bind events from my render()
method after I set up the templateElement
like so:
export class UserForm {
constructor(public parent: Element) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<input />
<button>Click Me</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()
this.parent.append(templateElement.content);
}
}
I need to pass in the DocumentFragment from the templateElement
I just created. The DocumentFragment is a reference to all the HTML that I am trying to pass into the DOM.
export class UserForm {
constructor(public parent: Element) {}
eventsMap(): { [key: string]: () => void } {
return {
"click:button": this.onButtonClick,
};
}
onButtonClick() {
console.log("Howdy");
}
template(): string {
return `<div>
<h1>User Form</h1>
<input />
<button>Click Me</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);
}
}
This is a clever way to handle events if you don't have something like JSX to find all your events for you.
Now I should be able to click on my button and see howdy
appear:
I now have an eventing system built into my View.