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)