This is a bonus post in a series I'm working covering web components.
Part 4, on the Polymer library, is on the way. While we're waiting, check out this neat problem a student approached me with that we can solve with web standards:
They were using a library to render a WebGL globe inside a Vue component. They wanted to generate a set of markers and then track which markers were opened and which were closed. The WebGL library provided some APIs for attaching a string of innerHTML
to each marker's popup, but didn't expose any APIs to track open, close, or click events.
I had a bit of a devilish thought ๐. If we can't add behaviour to the library popups, but we can add HTML, what if we added HTML that encapsulates its own behaviour?
๐ฉ Web Components to the Rescue!! ๐จโ๐ป
Defining <popup-tooltip>
What we needed was an HTML element that fires an event every time it's containing popup opens or closes. The WebGL lib used style="visibility: visible"
to open and close popups, so we'll create an element that uses MutationObserver
to observe it's own parents.
class PopupTooltip extends HTMLElement {
constructor() {
super();
this.observerCallback = this.observerCallback.bind(this);
this.attachShadow({mode: 'open'});
this.shadowRoot.appendChild(document.createElement('slot'));
this.observer = new MutationObserver(this.observerCallback);
}
connectedCallback() {
// HACK: WebGL library toggles style.visibility on it's own
// generated DOM to hide and show tooltips.
const libraryContainer = this.parentElement.parentElement.parentElement;
const config = { attributes: true, subtree: false, children: false };
this.observer.observe(libraryContainer, config);
}
observerCallback([{target}]) {
const visible = target.style.visibility === 'visible';
const type = 'popup-' + visible ? 'opened' : 'closed';
const bubbles = true;
const composed = true;
const detail = this;
this.dispatchEvent(new CustomEvent(type, { bubbles, composed, detail }));
}
}
customElements.define('popup-tooltip', PopupTooltip);
Connecting to the Vue Wrapper
So now we have a <popup-tooltip>
element which will fire a popup-opened
or popup-closed
event any time it's container's visibility is toggled by the WebGL library. We set up listener's in the private DOM of the wrapping Vue Component:
<!-- WebGL lib instanciates on '#earth' -->
<div id="earth" @popup-opened="onPopupOpened" @popup-closed="onPopupClosed"></div>
Creating Each Popup
Then when we instantiated the WebGL lib and passed our data, we set up the markers to display a <popup-tooltip>
element in it's tooltip content.
geoMarkers.forEach(marker => {
const location = marker.latLng;
const glMarker = new WebGLLib.popup({/*...*/});
// NB: popupHTML is **just HTML**, there's no framework or library here.
const popupHTML = `<popup-tooltip data-location="${location}">${marker.title}</popup-tooltip>`;
// `bindPopup` is a method on our WebGL library's marker API.
glMarker.bindPopup(popupHTML, config);
})
Profit!
The last thing we had to do was track which popups were opened and which closed.
onPopupOpened({target: {dataset: {location}}}) {
const [lat, lng] = location.split(',');
console.log(`opened: lat: ${lat} lng: ${lng}`);
}
You don't need to give up your frameworks to use web components. You can use them anywhere you can use HTML and JavaScript. That's precisely what made web-components a win here: our GL library didn't take Vue components as input, it took a string of HTML.
See you in a few days for part 4 on the Polymer library.
Would you like a one-on-one mentoring session on any of the topics covered here?
Top comments (0)