<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Counter HTML Template Web Component Demo</title>
<!-- Importing Web Components Polyfill -->
<script src="https://polygit.org/components/webcomponentsjs/webcomponents-loader.js"></script>
<!-- Import our custom element -->
<script src="vt-counter.js"></script>
<script src="vt-counter-html-template.js"></script>
<style>
body {
text-align: center;
}
</style>
</head>
<body>
<h1>Counter HTML Template Web Component Demo</h1>
<!-- Using our new custom element -->
<vt-counter-html-template first="10"></vt-counter-html-template>
<!-- For displaying event output -->
<div id="events"></div>
<script>
// Update the "events" div with the counter has been upgraded by the browser, and also when it fires custom events.
let events = '';
const eventElement = document.getElementById('events');
customElements.whenDefined('vt-counter').then(() => {
events += '<br>vt-counter upgraded';
eventElement.innerHTML = events;
});
document.addEventListener('vt-counter-started', function(event) {
events += '<br>Counter Started; value: ' + event.detail.value;
eventElement.innerHTML = events;
});
document.addEventListener('vt-counter-stopped', function(event) {
events += '<br>Counter Stopped; value: ' + event.detail.value;
eventElement.innerHTML = events;
});
</script>
</body>
</html>
Simple example counter using the Custom Elements and HTML Templates.
Repo: https://github.com/kito99/webcomponents-examples
Written by Kito Mann; brought to you by https://virtua.tech.
/**
vt-counter example custom element (v1 spec). Counts up from `first` (or 0 if `first` is
not defined). Starts and stops when a user clicks on it.
@author Kito D. Mann (kito-public at virtua dot com), http://virtua.tech
*/
class VirtuaTrainingCounter extends HTMLElement {
static get observedAttributes() {
return ['first'];
}
// Store properties as attributes so they work both ways.
get first() {
return this.getAttribute('first');
}
set first(first) {
this.value = Number(first);
this.setAttribute('first', first);
}
// Don't store this property as an attribute because it changes frequently.
get value() {
return this._value;
}
set value(value) {
const num = Number(value);
this._value = num;
this._content.innerText = num;
}
constructor() {
super();
console.log('inside constructor');
this._onClick = this._onClick.bind(this);
this.addEventListener('click', this._onClick);
}
/** Fires after an instance has been inserted into the document */
connectedCallback() {
console.log('inside connectedCallback');
this._content = document.createElement('span');
this._content.innerText = 'Counter';
this._content.className = 'counter-disabled';
this.appendChild(this._content);
this._upgradeProperty('first');
this.value = this.first || 0;
}
/**
* Fires after an instance has been removed from the document. Here
* we stop the timer and remove event listeners.
*/
disconnectedCallback() {
console.log('inside disconnectedCallback');
this.stop();
this.removeEventListener('click', this._onClick);
};
/**
* Fires after an attribute has been added, removed, or updated. Here we
* change the `value` to `first` and restart the timer if `first` changes.
*/
attributeChangedCallback(attr, oldVal, newVal) {
console.log('inside attributeChangedCallback', 'attr:', attr, 'oldVal:', oldVal, 'newVal:', newVal);
switch (attr) {
case 'first' :
// attributeChangedCallback is called before connectedCallback, so this._content
// may not be defined yet.
if (this._content) {
this.value = newVal;
this.stop();
this.start();
}
break;
}
}
/** Fires after an element has been moved to a new document */
adoptedCallback() {
console.log('inside adoptedCallback');
}
start() {
this._content.classList.remove('counter-disabled');
this._timer = setInterval(() => {
this.value++;
}, 2000);
this.dispatchEvent(new CustomEvent('vt-counter-started', {
detail: {
value: this.value,
},
bubbles: true
}));
}
stop() {
clearInterval(this._timer);
this._timer = null;
this._content.classList.add('counter-disabled');
this.dispatchEvent(new CustomEvent('vt-counter-stopped', {
detail: {
value: this.value,
},
bubbles: true
}));
}
_onClick() {
if (!this._timer) {
this.start();
}
else{
this.stop();
}
}
/**
* If the property was set before the element was upgraded,
* remove it and set it again so that the proper setter is used.
*/
_upgradeProperty(prop) {
if (this.hasOwnProperty(prop)) {
let value = this[prop];
delete this[prop];
this[prop] = value;
}
}
}
// Registers <vt-counter> as a custom element
window.customElements.define('vt-counter', VirtuaTrainingCounter);
(function() {
/**
* Declare the template here so it can be re-used by multiple instances of the element.
* Cloning from the template instead of using innerHTML is more performant since the markup
* is only parsed once.
*/
const template = document.createElement('template');
template.innerHTML = `
<style>
.counter {
color: indigo;
font-size: 60pt;
font-family: sans-serif;
text-align: center;
padding: 10px;
border: 1px black inset;
}
.counter:hover {
cursor: pointer;
}
.counter-disabled {
color: gray;
}
</style>
<span id="value" class="counter counter-disabled">Counter</span>
`;
/**
vt-counter-html-template example custom element (v1 spec)
@author Kito D. Mann (kito-public at virtua dot com), http://virtua.tech
*/
class VirtuaTrainingHtmlTemplateCounter extends VirtuaTrainingCounter {
/**
* Fires when an instance was inserted into the document.
* @override
*/
connectedCallback() {
console.log('inside overridden connectedCallback')
const templateContent = template.content.cloneNode(true);
this._content = templateContent.getElementById('value');
this.appendChild(templateContent);
this._upgradeProperty('first');
this.value = this.first || 0;
}
}
// Registers <vt-counter-html-template> as a custom element
window.customElements.define('vt-counter-html-template', VirtuaTrainingHtmlTemplateCounter);
})();