README
<mjo-functions>
Mixin to be able to use html events in mjo-polymer
web components. Because components render to a shadowDOM
we can sometimes find that we have to write a lot of extra code to listen for an event from an element within the shadowDOM
through javascript.
With mjo-functions
this work is simplified enough to make elements inside our components can call methods that are outside the shadowDOM
.
Consider the following example:
import { PolymerElement, html } from "../mjo-polymer/polymer/polymer-element.js";
class MyComponent extends PolymerElement {
static get template() {
return html`
<style>
:host {
position: relative;
display: block;
}
:host([unresoved]) {
display: none;
}
.text {
position: relative;
padding: 10px;
}
</style>
<div class="text">
Lorem ipsum dolor sit, amet consectetur adipisicing elit. Dolorem enim totam voluptatibus
repudiandae in quo temporibus recusandae cum soluta harum inventore eveniet incidunt officia
blanditiis voluptatum, itaque magni. Reiciendis, architecto.
</div>
<button on-click="_handleClick">MY BUTTON</button>
`;
}
/**
* @method connectedCallback
*/
connectedCallback() {
super.connectedCallback();
//Resolvemos el componente
this.removeAttribute("unresolved");
}
/**
* @method _handleClick
*
* Listen click on button element
*
* @param {event} ev
*/
_handleClick(ev) {
console.log("Target is " + ev.target);
}
}
window.customElements.define("my-component", MyComponent);
In the example above, if we click on the component's button, the _handleClick
method of the component itself will be called. This is the expected performance of Polymer web components.
But now suppose that by clicking on the button we want to call a function that is outside the component. This will require us to write javascript code outside of the component and prepare the component to send the event outside of it. Which can be a problem when reusing the component.
We can also create a CustomEvent
and listen to that event. It is not a bad solution, but if we have many components we can end up having a large number of custom events whose names can be difficult to remember and we can end up repeating event names if we do not follow a very orderly logic.
With mjo-functions
we try to simplify this problem by declaring functions in the global scope.
Now let's add the mixin
mjo-functions
to our component.
components/my-component.js
import { PolymerElement, html } from "../mjo-polymer/polymer/polymer-element.js";
import { MjoFunctions } from "/node_modules/mjo-functions/mjo-functions.js";
class MyComponent extends MjoFunctions(PolymerElement) {
static get template() {
return html`
...
<button on-click="_handleClick">MY BUTTON</button>
`;
}
...
_handleClick(ev) {
// Check is `this.onClick` is a function. If true, call them
if(typeof this.onClick === "function" ) {
this.onClick(ev.target, this, ev);
}
}
}
window.customElements.define("my-component", MyComponent);
What happened here? The MjoFunctions
mixin adds properties to our component for most html events and touch events with a camelCase format. This means that our component right now has the following properties onClic, onMouseenter, onMouseleave, onTouchstart ...
. You will only have to be careful not to add properties to the component that do not match the properties that are added by the MjoFunctions
mixin
All available events are defined here Events Attributes, Touch Events. Obviously the Events attributes belonging to the window
have been omitted.
Note well that the rule that MjoFunctions
follows when adding the properties of the events is to use a camelCase, so that the html attribute on-mouseenter
that we will add to our component, becomes the onMouseenter
property within the component. This is a feature of Polymer which is specified in your documentation.
So now we can use our component
<script type="module" src="components/my-component.js"></script>
<my-component onclick="componentClicked" on-click="buttonClicked"></my-component>
<script>
function componentClicked(ev) {
console.log(ev);
}
function buttonClicked(target, component, ev) {
console.log(target);
console.log(component);
console.log(ev);
}
</script>
As we can see in the example above, we can see that two html events have been added but with a slightly different syntax. With the onclick
we will call the componentClicked
function, only this event will be called when we click anywhere in our component. However, with the on-click
we will call the buttonClicked
and this event will only be fired when the button inside our component is clicked.
This works fine because the buttonClicked
function is declared in a global scope. But suppose we want to listen to a function that is inside a javascript module. The classes and functions that we create within javascript modules will not be available in the global scope. Or we can also have another component that does not contain the my-component
component inside, but that nevertheless we want it to have a logic linked to the button inside our component and to do something when we click on our component.
We are going to create a javascript module and another component and in both we will create a logic linked with the button of our my-component
component.
/modules/my-module.js
import { GlobalsFunctions } from "/node_modules/mjo-functions/mjo-functions.js";
export default class MyModule {
constructor() {
GlobalsFunctions.add("MyModuleButtonEnter", this.buttonMyComponentClicked.bind(this));
console.log(window.$mjo);
}
buttonMyComponentEnter(target, component, ev) {
console.log(target);
console.log(component);
console.log(ev);
}
}
/components/my-second-component.js
import { PolymerElement, html } from "../mjo-polymer/polymer/polymer-element.js";
import { GlobalsFunctions } from "/node_modules/mjo-functions/mjo-functions.js";
class MySecondComponent extends PolymerElement {
static get template() {
return html` ... `;
}
connectedCallback() {
super.connectedCallback();
GlobalsFunctions.add("SecondComponentButtonClicked", this.buttonMyComponentClicked.bind(this));
console.log(window.$mjo);
}
disconectedCallback() {
super.disconectedCallback();
GlobalsFunctions.remove("SecondComponentButtonClicked");
}
buttonMyComponentClicked(target, component, ev) {
console.log(target);
console.log(component);
console.log(ev);
}
}
window.customElements.define("my-second-component", MySecondComponent);
What happened here?
With the GlobalsFunctions
object, we can create functions that pass to the global scope that can be called within the component since they are now in the global scope. The GlobalsFunctions.add
method creates a global function that always has thiswindow. $ Mjo [functionName]
structure that is accessible from any component or module since it is called within the window
object. In addition, you make sure that there can never be functions with repeated names in the window. $ Mjo
object since it triggers an error in the console when trying to enter two functions with the same name.
Now let's modify our component so that it can call these functions when interacting with the button
components/my-component.js
import { PolymerElement, html } from "../mjo-polymer/polymer/polymer-element.js";
import { MjoFunctions } from "/node_modules/mjo-functions/mjo-functions.js";
class MyComponent extends MjoFunctions(PolymerElement) {
static get template() {
return html`
...
<button on-click="_handleClick" on-mouseenter="_handleMouseenter">MY BUTTON</button>
`;
}
...
__handleMouseenter(ev) {
// Check is `this.onClick` is a function. If true, call them
if(typeof this.onMouseenter === "function" ) {
this.onMouseenter(ev.target, this, ev);
}
}
_handleClick(ev) {
// Check is `this.onClick` is a function. If true, call them
if(typeof this.onClick === "function" ) {
this.onClick(ev.target, this, ev);
}
}
}
window.customElements.define("my-component", MyComponent);
And now let's use our component
<script type="module" src="components/my-component.js"></script>
<my-component
on-click="$mjo.SecondComponentButtonClicked"
on-mouseenter="$mjo.MyModuleButtonEnter"
></my-component>
Now our component will call the methods that are inside the my-second-component
component and the MyModule
module
You can see a working example in CODESANDBOX