Ejemplo webcomponent – ddr-counter

Hola a todos, hoy os voy explicar como podemos hacer un pequeño contador como webcomponent.

Vamos a realizar paso a paso un pequeño contador como este:

Te recomiendo leer antes este articulo:

Creando nuestro primer webcomponent

Empezaremos definiendo la clase:

class Counter extends HTMLElement {

    constructor() {
        super();
    }

}

customElements.define('ddr-counter', Counter);

Vamos a crear los siguientes atributos:

  • value: valor actual del input
  • step: paso al que va incremento/decremento.
  • min: valor minimo permitido.
  • max: valor maximo permitido.

Recuerda que se realiza haciendo simplemente get y set.

class Counter extends HTMLElement {

    constructor() {
        super();
    }

    get value() {
        return this.getAttribute('value');
    }

    set value(value) {
        this.setAttribute('value', value);
    }

    get step() {
        return this.getAttribute('step');
    }

    set step(value) {
        this.setAttribute('step', value);
    }

    get min() {
        return this.getAttribute('min');
    }

    set min(value) {
        this.setAttribute('min', value);
    }

    get max() {
        return this.getAttribute('max');
    }

    set max(value) {
        this.setAttribute('max', value);
    }


}

customElements.define('ddr-counter', Counter);

Ahora vamos a crear su propio HTML y CSS.

class Counter extends HTMLElement {

    constructor() {
        super();
    }

    get value() {
        return this.getAttribute('value');
    }

    set value(value) {
        this.setAttribute('value', value);
    }

    get step() {
        return this.getAttribute('step');
    }

    set step(value) {
        this.setAttribute('step', value);
    }

    get min() {
        return this.getAttribute('min');
    }

    set min(value) {
        this.setAttribute('min', value);
    }

    get max() {
        return this.getAttribute('max');
    }

    set max(value) {
        this.setAttribute('max', value);
    }

    
    render() {
        const container = document.createElement("div");
        container.innerHTML = `
        <style>
            .ddr-counter #input_counter {
                border-radius: 10px;
                text-align: center;
            }
        </style>
        <div class='ddr-counter'>
            <button id='b_increment'>+</button>
            <input id='input_counter' type='text' readonly='true' />
            <button id='b_decrement'>-</button>
        </div>`;
        
        let shadow = this.attachShadow({ mode: 'open' });
        shadow.appendChild(container);

    }


}

customElements.define('ddr-counter', Counter);

La idea es añadir en una cadena el contenido en HTML y CSS (en una etiqueta style).

Para que nos sea más fácil, vamos a obtener como atributos propios, los botones y el input.

Lo podemos hacer con querySelector, te recomiendo poner id o clases propias.

class Counter extends HTMLElement {

    constructor() {
        super();
    }

    get value() {
        return this.getAttribute('value');
    }

    set value(value) {
        this.setAttribute('value', value);
    }

    get step() {
        return this.getAttribute('step');
    }

    set step(value) {
        this.setAttribute('step', value);
    }

    get min() {
        return this.getAttribute('min');
    }

    set min(value) {
        this.setAttribute('min', value);
    }

    get max() {
        return this.getAttribute('max');
    }

    set max(value) {
        this.setAttribute('max', value);
    }

    
    render() {
        const container = document.createElement("div");
        container.innerHTML = `
        <style>
            .ddr-counter #input_counter {
                border-radius: 10px;
                text-align: center;
            }
        </style>
        <div class='ddr-counter'>
            <button id='b_increment'>+</button>
            <input id='input_counter' type='text' readonly='true' />
            <button id='b_decrement'>-</button>
        </div>`;
        
   
        let shadow = this.attachShadow({ mode: 'open' });
        shadow.appendChild(container);

        // Asociamos el boton de incremento
        const b1 = shadow.querySelector('#b_increment');
        this.buttonIncrement = b1;

        // Asociamos el input del counter
        const input = shadow.querySelector('#input_counter');
        this.inputText = input;
        
        // Asociamos el boton de decremento
        const b2 = shadow.querySelector('#b_decrement');
        this.buttonDecrement = b2;

    }


}

customElements.define('ddr-counter', Counter);

En la función connectedCallback, que se llama cuando se carga el componente, llamaremos a esta función que acabamos de realizar.

También si tenemos un valor inicial en el atributo value, lo asociaremos. En el caso de que tenga un mínimo y no le damos un valor, lo iniciamos a ese valor mínimo

class Counter extends HTMLElement {

    constructor() {
        super();
    }

    get value() {
        return this.getAttribute('value');
    }

    set value(value) {
        this.setAttribute('value', value);
    }

    get step() {
        return this.getAttribute('step');
    }

    set step(value) {
        this.setAttribute('step', value);
    }

    get min() {
        return this.getAttribute('min');
    }

    set min(value) {
        this.setAttribute('min', value);
    }

    get max() {
        return this.getAttribute('max');
    }

    set max(value) {
        this.setAttribute('max', value);
    }

    connectedCallback() {
        
        this.render();
        
        if(!this.value && this.min){
            this.value = this.min;
        }else if(!this.value){
            this.value = 0;
        }
        this.inputText.setAttribute('value', this.value);
        
    }
    
    render() {
        const container = document.createElement("div");
        container.innerHTML = `
        <style>
            .ddr-counter #input_counter {
                border-radius: 10px;
                text-align: center;
            }
        </style>
        <div class='ddr-counter'>
            <button id='b_increment'>+</button>
            <input id='input_counter' type='text' readonly='true' />
            <button id='b_decrement'>-</button>
        </div>`;
        
   
        let shadow = this.attachShadow({ mode: 'open' });
        shadow.appendChild(container);

        // Asociamos el boton de incremento
        const b1 = shadow.querySelector('#b_increment');
        this.buttonIncrement = b1;

        // Asociamos el input del counter
        const input = shadow.querySelector('#input_counter');
        this.inputText = input;
        
        // Asociamos el boton de decremento
        const b2 = shadow.querySelector('#b_decrement');
        this.buttonDecrement = b2;

    }


}

customElements.define('ddr-counter', Counter);

Ahora, vamos a hacer la función que se realizará cuando pulsemos el botón de incremento.

Los pasos que haremos son los siguientes:

  • Obtenemos el paso, sino le indicamos ninguno, seria 1.
  • Sumamos el valor con el paso.
  • Si hay un máximo y el valor es mayor que el máximo, el valor sera el máximo sino el valor calculado.
  • Asociamos el valor al input.

class Counter extends HTMLElement {

    constructor() {
        super();
    }

    connectedCallback() {
        
        this.render();
      
        if(!this.value && this.min){
            this.value = this.min;
        }else if(!this.value){
            this.value = 0;
        }
        this.inputText.setAttribute('value', this.value);
        
    }

    increment() {

        // Parseo el paso
        let step = +this.step; // parseInt(this.step)

        // Sino existe el paso, por defecto es 1
        if (!step) {
            step = 1;
        }

        // Sumamos el valor al paso
        const nextValue = +this.value + step;

        // Si existe un maximo y el valor es mayor que el maximo
        // Ponemos el maximo como valor 
        if (this.max && nextValue > +this.max) {
            this.value = this.max;
        } else {
            this.value = nextValue;
        }

        // Asociamos el valor
        this.inputText.setAttribute('value', this.value);
    }

    get value() {
        return this.getAttribute('value');
    }

    set value(value) {
        this.setAttribute('value', value);
    }

    get step() {
        return this.getAttribute('step');
    }

    set step(value) {
        this.setAttribute('step', value);
    }

    get min() {
        return this.getAttribute('min');
    }

    set min(value) {
        this.setAttribute('min', value);
    }

    get max() {
        return this.getAttribute('max');
    }

    set max(value) {
        this.setAttribute('max', value);
    }

    
    render() {
        const container = document.createElement("div");
        container.innerHTML = `
        <style>
            .ddr-counter #input_counter {
                border-radius: 10px;
                text-align: center;
            }
        </style>
        <div class='ddr-counter'>
            <button id='b_increment'>+</button>
            <input id='input_counter' type='text' readonly='true' />
            <button id='b_decrement'>-</button>
        </div>`;
`;
        
   
        let shadow = this.attachShadow({ mode: 'open' });
        shadow.appendChild(container);

        // Asociamos el boton de incremento
        const b1 = shadow.querySelector('#b_increment');
        this.buttonIncrement = b1;

        // Asociamos el input del counter
        const input = shadow.querySelector('#input_counter');
        this.inputText = input;
        
        // Asociamos el boton de decremento
        const b2 = shadow.querySelector('#b_decrement');
        this.buttonDecrement = b2;

    }

}

customElements.define('ddr-counter', Counter);

Lo mismo con el de decremento, solo que en lugar de usar el máximo, usamos el mínimo.


class Counter extends HTMLElement {

    constructor() {
        super();
    }

    connectedCallback() {
        
        this.render();
        
        if(!this.value && this.min){
            this.value = this.min;
        }else if(!this.value){
            this.value = 0;
        }
        this.inputText.setAttribute('value', this.value);
        
    }

    increment() {

        // Parseo el paso
        let step = +this.step; // parseInt(this.step)

        // Sino existe el paso, por defecto es 1
        if (!step) {
            step = 1;
        }

        // Sumamos el valor al paso
        const nextValue = +this.value + step;

        // Si existe un maximo y el valor es mayor que el maximo
        // Ponemos el maximo como valor 
        if (this.max && nextValue > +this.max) {
            this.value = this.max;
        } else {
            this.value = nextValue;
        }

        // Asociamos el valor
        this.inputText.setAttribute('value', this.value);
    }

    decrement() {
        
        // Parseo el paso
        let step = +this.step; // parseInt(this.step)

        // Sino existe el paso, por defecto es 1
        if (!step) {
            step = 1;
        }

        // Restamos el valor al paso
        const nextValue = +this.value - step;

        // Si existe un minimo y el valor es menor que el maximo
        // Ponemos el minimo como valor 
        if (this.min && nextValue < +this.min) {
            this.value = this.min;
        } else {
            this.value = nextValue;
        }

        // Asociamos el valor
        this.inputText.setAttribute('value', this.value);
    }

    get value() {
        return this.getAttribute('value');
    }

    set value(value) {
        this.setAttribute('value', value);
    }

    get step() {
        return this.getAttribute('step');
    }

    set step(value) {
        this.setAttribute('step', value);
    }

    get min() {
        return this.getAttribute('min');
    }

    set min(value) {
        this.setAttribute('min', value);
    }

    get max() {
        return this.getAttribute('max');
    }

    set max(value) {
        this.setAttribute('max', value);
    }

    
    render() {
        const container = document.createElement("div");
        container.innerHTML = `
        <style>
            .ddr-counter #input_counter {
                border-radius: 10px;
                text-align: center;
            }
        </style>
        <div class='ddr-counter'>
            <button id='b_increment'>+</button>
            <input id='input_counter' type='text' readonly='true' />
            <button id='b_decrement'>-</button>
        </div>`;
        
   
        let shadow = this.attachShadow({ mode: 'open' });
        shadow.appendChild(container);

        // Asociamos el boton de incremento
        const b1 = shadow.querySelector('#b_increment');
        this.buttonIncrement = b1;

        // Asociamos el input del counter
        const input = shadow.querySelector('#input_counter');
        this.inputText = input;
        
        // Asociamos el boton de decremento
        const b2 = shadow.querySelector('#b_decrement');
        this.buttonDecrement = b2;

    }

}

customElements.define('ddr-counter', Counter);

Nos falta asociar estos eventos a los botones con addEventListener en connectedCallback y bind en el constructor, sino lo hacemos no funcionaran al pulsar sobre ellos.

La función bind lo que hace es crear una nueva función que forzará a this dentro de la función a ser el parámetro pasado a bind().


class Counter extends HTMLElement {

    constructor() {
        super();

        // Hacemos bind para que podamos llamarlo en los addEventListener
        this.increment = this.increment.bind(this);
        this.decrement = this.decrement.bind(this);

    }

    connectedCallback() {
        
        this.render();
        
        // Asociamos el click de los botones a su función
        this.buttonIncrement.addEventListener('click', this.increment);
        this.buttonDecrement.addEventListener('click', this.decrement);

        if(!this.value && this.min){
            this.value = this.min;
        }else if(!this.value){
            this.value = 0;
        }
        this.inputText.setAttribute('value', this.value);
        
    }

    increment() {

        // Parseo el paso
        let step = +this.step; // parseInt(this.step)

        // Sino existe el paso, por defecto es 1
        if (!step) {
            step = 1;
        }

        // Sumamos el valor al paso
        const nextValue = +this.value + step;

        // Si existe un maximo y el valor es mayor que el maximo
        // Ponemos el maximo como valor 
        if (this.max && nextValue > +this.max) {
            this.value = this.max;
        } else {
            this.value = nextValue;
        }

        // Asociamos el valor
        this.inputText.setAttribute('value', this.value);
    }

    decrement() {
        
        // Parseo el paso
        let step = +this.step; // parseInt(this.step)

        // Sino existe el paso, por defecto es 1
        if (!step) {
            step = 1;
        }

        // Restamos el valor al paso
        const nextValue = +this.value - step;

        // Si existe un minimo y el valor es menor que el maximo
        // Ponemos el minimo como valor 
        if (this.min && nextValue < +this.min) {
            this.value = this.min;
        } else {
            this.value = nextValue;
        }

        // Asociamos el valor
        this.inputText.setAttribute('value', this.value);
    }

    get value() {
        return this.getAttribute('value');
    }

    set value(value) {
        this.setAttribute('value', value);
    }

    get step() {
        return this.getAttribute('step');
    }

    set step(value) {
        this.setAttribute('step', value);
    }

    get min() {
        return this.getAttribute('min');
    }

    set min(value) {
        this.setAttribute('min', value);
    }

    get max() {
        return this.getAttribute('max');
    }

    set max(value) {
        this.setAttribute('max', value);
    }

    
    render() {
        const container = document.createElement("div");
        container.innerHTML = `
        <style>
            .ddr-counter #input_counter {
                border-radius: 10px;
                text-align: center;
            }
        </style>
        <div class='ddr-counter'>
            <button id='b_increment'>+</button>
            <input id='input_counter' type='text' readonly='true' />
            <button id='b_decrement'>-</button>
        </div>`;
        
   
        let shadow = this.attachShadow({ mode: 'open' });
        shadow.appendChild(container);

        // Asociamos el boton de incremento
        const b1 = shadow.querySelector('#b_increment');
        this.buttonIncrement = b1;

        // Asociamos el input del counter
        const input = shadow.querySelector('#input_counter');
        this.inputText = input;
        
        // Asociamos el boton de decremento
        const b2 = shadow.querySelector('#b_decrement');
        this.buttonDecrement = b2;

    }

}

customElements.define('ddr-counter', Counter);

Con esto tendríamos nuestro webcomponent ya listo para funcionar. Lo probamos en un HTML.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="main.js"></script>
</head>
<body>
    <ddr-counter step="5" min="0" max="100"></ddr-counter>
</body>
</html>

Te dejo el proyecto en stackblitz.

Aquí os dejo videos donde hacemos el webcomponent en el canal de youtube.

Espero que os sea de ayuda. Si tenéis dudas, preguntad. Estamos para ayudarte.

Etiquetas

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *