Few years back, I have learned about Web Components. However, I did not get a chance to work on Web Components yet. It's been on my do to list to experiment Web Components, especially with in APEX. My current staycation gave me an opportunity to do this experiment.
Web Components
Web Components is a suite of different technologies allowing you to create reusable custom elements — with their functionality encapsulated away from the rest of your code — and utilize them in your web apps.
In other words, using Web Components, we can create custom elements which are completely self contained with all HTML, CSS and JavaScript (JS) encapsulated in one place. And then we can use these new custom element in our web applications with out the risk of code conflicts. Functionality wise, they are similar to jQuery widgets, however to implement Web Components we don't need any additional libraries, all modern browsers natively support underlying technologies. If you are new to Web Components, then I suggest you to go through Web Components Crash Course video or this article. Above quoted definition is taken from this article. Example we are going to discuss is inspired from the above crash course video.
For the demo purpose, let's consider an example. We want to create a new custom element country-info with an attribute countrycode. When we specify countrycode, then this element should display basic information about the country, by making call to an existing RESTful API.
HTML Source
<country-info countrycode="IN"></country-info>
Target Output
Web Components mainly consists of three main technologies.
- HTML Templates
- Custom Elements
- Shadow DOM
HTML Templates
// JavaScript code // create template element const template = document.createElement('template'); // Write HTML and CSS into the template // with Shadow DOM, CSS defined here will be applicable to custom elements alone // CSS defined at page level won't be applicable to custom elements template.innerHTML = ` <style> div.country-data { background: #F5F4F2; width: 400px; margin-bottom: 10px; border-bottom: purple 8px solid; padding: 4px; } div.country-data h2{ color: purple; } </style> <div class="country-data"> <h2></h2> <div class="population">Population: <span></span> Millions</div> <div class="area">Area: <span></span> thousand sq.km.</div> </div> `;
Custom Elements + Shadow DOM
// define class for the custom element // general convention followed : if the custom element name is custom-element, then CustomElement is used as class name // our custom element name is country-info, so lets define CountryInfo as class name class CountryInfo extends HTMLElement { constructor() { // always call super first in constructor, so that the correct prototype chain is established. super(); // attach Shadow DOM, this isolates the custom element this.attachShadow({ mode: 'open' }); // clone the template content and append to shadowRoot this.shadowRoot.appendChild(template.content.cloneNode(true)); // fetch country data from RESTful API // here, we are reading custom attribute countrycode value and using it as input for RESTful API call // fill template with data from RESTful API response fetch('https://apex.oracle.com/pls/apex/hari/country-data/country/' + this.getAttribute('countrycode')).then(response => response.json()).then(data => { this.shadowRoot.querySelector('h2').innerText = data.name; this.shadowRoot.querySelector('div.population > span').innerText = data.population; this.shadowRoot.querySelector('div.area > span').innerText = data.area; }); } } // map country-info tag to use CountryInfo class window.customElements.define('country-info', CountryInfo);
Using Web Components in APEX
To use this Web Component in APEX, let's save all this JS code to a file. I have copied template code, CountryInfo class code to a JS file. I have used same name for custom element and to the JS file. Having same name for both custom element and corresponding JS file is not a requirement, but it's a good practice. So, it our case, JS file name is country-info.js. Let's upload it in "APEX Application > Shared Components > Static Application Files" section.<script type="module" src="#APP_IMAGES#country-info.js"></script>
- We are not using standard "Page > JavaScript > File URLs" section to load this JS file, instead we are writing script tag in HTML Header section.
- Script tag has type attribute with module as it's value.
console.log(template.innerHTML);
<country-info countrycode="IN"></country-info>
Adding User Interaction
<div id="country_data_container"></div>
- Name: Display Country Data (or any proper name)
- When:
- Event: Change
- Selection Type: jQuery Selector
- jQuery Selector: input[name=P35_COUNTRIES]
- True Action:
- Action: Execute JavaScript Code
- Settings:
- Code: As shown below
// get country code from the checkbox item let countryCode = $(this.triggeringElement).val(); // get country-info tag for the countryCode, if it's previously added to the DOM let countryNode = $("country-info[countrycode=" + countryCode + "]"); if ($(this.triggeringElement).is(':checked')) { // check if element already exists, then just show it if (countryNode.length == 1) countryNode.show(); else // otherwise add it to DOM $('#country_data_container').append('<country-info countrycode="' + countryCode + '"></country-info>'); } else { // unchecked, hide it countryNode.hide(); }
As I said above, this is a little experiment I did with the Web Components. This is a simple Web Component and we are not using template slots, custom element lifecycle callbacks etc. You can refer webcomponents.org to find out some useful and advanced Web Components published by others.
Comments