TheRogerLAB Codes

Rating stars web component

  July, 2021

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 1: Add a constructor

class ratingLab extends HTMLElement {
  constructor () {
    super();
  }
}
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:

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:

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 6: Add a function for get the component's value

class ratingLab extends HTMLElement {
  //Step 1

  //Step 2

  //Step 3

  //Step 4

  //Step 5

  getValue(){
    var e = this.shadowRoot.querySelectorAll('input');
    var v = 0;
    for(var i = 0; i < e.length; i++){
      if(e[i].checked == true)
        v = e[i].value;
    } 
    return v;
  }

}
window.customElements.define('rating-lab', ratingLab);

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:

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:

SEE DEMO RATE THIS ARTICLE
4.3
15
TheRogerLAB Codes
Powered by TheRogerLAB Sandbox

info@therogerlab.com