Introduction
This component consists in a rating stars element.
Let's start by creating the structure of our web component. Create a file ratingLab.js:
class ratingLab extends HTMLElement {
//Here will be all the functionality.
}
window.customElements.define('rating-lab', ratingLab);
Step 2: Add the observed attributes
class ratingLab extends HTMLElement {
//Step 1
static get observedAttributes() {
return ['value'];
}
}
window.customElements.define('rating-lab', ratingLab);
In this case we create one attribute: value. When you include them in the observedAttributes function you are saying that they will be observed in case to change at some point.
Step 3: Adding getters
class ratingLab extends HTMLElement {
//Step 1
//Step 2
get value() {
return this.hasAttribute('value');
}
get size() {
return this.hasAttribute('size');
}
get color() {
return this.hasAttribute('color');
}
}
window.customElements.define('rating-lab', ratingLab);
In this case we create two more attributes. These two are not observables:
- size: The stars size. By default 20.
- color: The selected stars color. By default #ffc700.
Step 4: Add a function for set the selected stars color
class ratingLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
setColor(val) {
if(val!='0') {
//setting all to gray
for(var i = 1; i <= 5; i++){
this.shadowRoot.querySelector('[for=star'+i+']').style.color = '#c3c3c3';
}
//setting selected ones to defined color
for(var i = 1; i <= val; i++){
this.shadowRoot.querySelector('[for=star'+i+']').style.color = this.getAttribute('color');
}
} else {
//setting all to gray
for(var i = 1; i <= 5; i++){
this.shadowRoot.querySelector('[for=star'+i+']').style.color = '#c3c3c3';
}
}
}
}
window.customElements.define('rating-lab', ratingLab);
This function has one param:
- val: Is the value from 0 to 5 (stars).
Step 5: Add a function for set the component's value
class ratingLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
//Step 4
setValue(val) {
//if it is not 0
if(val!='0'){
//always round the result
var v = Math.round(val);
var e = this.shadowRoot.querySelectorAll('input');
//if value is defined then the stars wil be disabled
for(var i = 0; i < e.length; i++){
e[i].checked = false;
e[i].setAttribute('disabled','disabled');
}
//checking the clicked one
this.shadowRoot.querySelector('#star'+v).checked = true;
//change color if it set
if(this.color)
this.setColor(v);
} else {
var e = this.shadowRoot.querySelectorAll('input');
for(var i = 0; i < e.length; i++){
e[i].checked = false;
e[i].setAttribute('disabled','disabled');
}
//change color if it set
if(this.color)
this.setColor('0');
}
}
}
window.customElements.define('rating-lab', ratingLab);
⚠️ Notice that if the value is defined, then the stars will not be clickable.
Step 7: Add a attributeChangedCallback function that will be fired when a observable attribute change
class ratingLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
//Step 4
//Step 5
//Step 6
attributeChangedCallback(name, oldValue, newValue) {
if(this.shadowRoot){
if(this.value) {
this.setValue(this.getAttribute('value'));
}
}
}
}
window.customElements.define('rating-lab', ratingLab);
Notice that you can use setValue function or change the value attribute directly.
Step 8: Add a function connectedCallback
It fires when the component is created
class ratingLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
//Step 4
//Step 5
//Step 6
//Step 7
connectedCallback () {
//Here will be the functionality.
}
}
window.customElements.define('rating-lab', ratingLab);
Inside connectedCallback function we will create two things:
- shadowRoot: Is the shadowDOM of the component. It contains the styles of the component and its html definition.
- Conditions to apply certain functions to the component.
Filling the connectedCallback function:
class ratingLab extends HTMLElement {
//Step 1
//Step 2
//Step 3
//Step 4
//Step 5
//Step 6
//Step 7
connectedCallback () {
let shadowRoot = this.attachShadow({mode: 'open'});
shadowRoot.innerHTML = `
<style>
.rate {
float: left;
padding: 0 10px;
}
.rate:not(:checked) > input {
position:absolute;
top:-9999px;
}
.rate:not(:checked) > label {
float:right;
width:1em;
overflow:hidden;
white-space:nowrap;
cursor:pointer;
font-size:20px;
color:#c3c3c3;
}
.rate:not(:checked) > label:before {
content: '★ ';
}
.rate > input:checked ~ label {
color: #ffc700;
}
</style>
<div class="rate">
<slot name='textValue'></slot>
<input type="radio" id="star5" name="rate" value="5"/>
<label for="star5"></label>
<input type="radio" id="star4" name="rate" value="4"/>
<label for="star4"></label>
<input type="radio" id="star3" name="rate" value="3"/>
<label for="star3"></label>
<input type="radio" id="star2" name="rate" value="2"/>
<label for="star2"></label>
<input type="radio" id="star1" name="rate" value="1"/>
<label for="star1"></label>
<slot name='textTotal'></slot>
<slot name='rateButton'></slot>
</div>`;
//if it has a default value
if(this.value){
this.setValue(this.getAttribute('value'));
}
//if it has a default size
if(this.size){
var e = shadowRoot.querySelectorAll('label');
for(var i = 0; i < e.length; i++){
e[i].style.fontSize = this.getAttribute('size') +'px';
}
}
//if it has a default color
if(this.color){
if(!this.value){
var col = this.getAttribute('color');
//attaching a event listener
shadowRoot.querySelector('[for=star1]').addEventListener('click', e => {
this.setValue('1');
});
shadowRoot.querySelector('[for=star2]').addEventListener('click', e => {
this.setValue('2');
});
shadowRoot.querySelector('[for=star3]').addEventListener('click', e => {
this.setValue('3');
});
shadowRoot.querySelector('[for=star4]').addEventListener('click', e => {
this.setValue('4');
});
shadowRoot.querySelector('[for=star5]').addEventListener('click', e => {
this.setValue('5');
});
}
}
}
}
window.customElements.define('rating-lab', ratingLab);
Check a working demo for this component: