Utilizing Javascript’s event to produce a dynamic web component.

How to write a reusable html button

Kunta Hutabarat
5 min readFeb 26, 2021

Sometimes you encounter a situation that you wish the html element is customizable depending on a state, such as a button that will display different text and execute different functions depending on some data related to the button. It will be reusable. Hence you can display it multiple times such as in rows of a table. You can accomplish this using a web component.

I would like to share what I have learned in implementing a web component with that behavior and how to use Javascript’s event to solve the problem elegantly. In this example, I created a button that will show different text according to some data, and when it is clicked, the button will invoke a function defined at the parent page. As an example, the picture below shows a table with rows of students where the button will display a text that depends on the information of the status of the student. The button is a web component. In a typical web component, the button will define the function that will be executed upon clicking. In this case, the page that includes the web component will define the function of the click, such as assigning a score or verifying a payment. It makes sense, since the page has the context of what needs to be done upon clicking.

A multi-column table that lists students name with a button of actions for each row.
A table with different action button depends on the state of the student

The solution

I am using the Javascript framework called Polymer (a.k.a LitElement). To simplify the explanation, I called the page that declares the button as the parent, and the web component, or the button, as the child. The web component is called ‘csm-action-controller’ so you identify them in the code. The implementation was using Polymer version 2, although I believe this is a basic behavior so it should be the same in the latter version.

Displaying a different text on the button can be done easily by passing the text in the html attributes of the web component, as shown in the following section of code. The parent code shows how it will declare the button “csm-action-controller” with the text in the ‘label’ html attribute, and the child code shows how it will include the text as it displays the button.

Parent code:

<csm-action-controller 
id="action-controller"
labelName="Verify Payment">
</csm-action-controller>

Child code:

<dom-module id="csm-action-controller">
<template>
<button class="green>
[[labelName]]
</button>
static get properties() {
return {
labelName: String,
}
}
</template>
</dom-module>

The challenging part for me was how to execute the parent’s function when the button is clicked. It will give the flexibility to the parent to define the executable function. There are two methods to solve it. First is to use a callback mechanism, where the parent component passes a callback function to the child component, and the child will call it upon clicking. The second one is to use an event mechanism where the event from the child will bubble up to the parent and the parent reacts as it detects the event.

In the first solution, we are using the html attribute of the web component to pass the callback function. The web component will call the function upon receiving the click. It is pretty straight forward! Here is how it works. The web component has a property named ‘functionCallback’ of a String type. This property is the HTML attribute of the web component. The parent component passes its callback function name through the HTML attribute. The callback function is defined in the parent component. This approach works to an extent, since the callback function does not have access to the parent’s properties. The reason is because the function executes within the child component. I have never seen any documentation for this behavior, so I am not sure whether this is a supported behavior or not.

Parent code:

<dom-module id="csm-parent">
<template>
<csm-action-controller
id="action-controller"
labelName="Verify Payment"
function-callback="[[doAction]]">
</csm-action-controller>
</template>
<script>
class CsmParent extends Polymer.Element {
doAction(e){
console.log("Function callback works");
}
}
</script>
</dom-module>

Child code:

<dom-module id="csm-action-controller">
<template>
<button on-click="functionCallback" class="green">
[[labelName]]
</button>
</template>
<script>
class CsmActionController extends Polymer.Element {
static get is() {
return 'csm-action-controller';
}
static get properties() {
return {
functionCallback: String,
}
}
}
</script>
</dom-module>

The second solution is using Javascript’s event mechanism. The event mechanism is documented clearly and in my opinion is better suited for this case. This is how it works, when somebody clicks the button in the child components, the generated event (by default) will bubble up to the parent. The parent needs to listen to the event and execute the callback function accordingly when the event is triggered. Since the function is executed in the parent, it also has access to all of the parent’s properties, and hence can have a better control of the action.

Parent code:

<dom-module id="csm-parent">
<template>
<csm-action-controller
id="action-controller1"
labelName="Verify Payment">
</csm-action-controller>
</template>
<script>
class CsmParent extends Polymer.Element {
ready(){
super.ready();
this.shadowRoot.querySelector('#action-controller1').addEventListener('click', e => {this._doAction(e)});
}
_doAction(e){
console.log("Button is clicked");
}
}
</script>
</dom-module>

Child code:

<dom-module id="csm-action-controller">
<template>
<button class="green">
[[labelName]]
</button>
</template>
</dom-module>

Finally ..

The solution above works fine if the parent has only a single web component button that will emit the event. The situation is more complicated if the parent has multiple buttons dynamically created through an iteration, such as using a <dom-repeat>. The picture in the beginning that shows buttons inside a table is one example. In this situation, it does not make sense to try to listen to each of the dynamically created child components. The solution for this problem is for the parent to listen to the event and determine which button that emits the event and execute the function accordingly. The event’s composedPath()[0] is representing the original target of the event, and hence can be used to interrogate the children’s attributes to determine the source button. One example is by reading the HTML attribute’s index from the button inside the child’s web component, as shown in the following child code.

Parent code:

<dom-module id="csm-parent">
<template>
<template is="dom-repeat"
items="{{items}}"
itemsIndexAs="itemsIndex">
<csm-action-controller
id="action-controller"
index="[[itemsIndex]]">
</csm-action-controller>
</template>
</template>
<script>
class CsmParent extends Polymer.Element {
ready(){
super.ready();
this.addEventListener('click', e => {this._doAction(e)});
}
_doAction(e){
let index = e.composedPath()[0].index;
console.log("Button " + index + " is clicked");
}
}
</script>
</dom-module>

Child code:

<dom-module id="csm-action-controller">
<template>
<button index="[[index]]" class="green">
[[labelName]]
</button>
</template>
</dom-module>

I hope this is useful. Please let me know your thoughts.

--

--

Kunta Hutabarat

Dev Manager, Developer, Entrepreneur in EdTech, creating tools to increase hands-on learning opportunities in STEM.