Utilizando reactividad
Introducción
En esta opurtunidad veremos como realizar validaciones en salesforce siguiendo las buenas practicas de los lightning web components sacando provecho de su reactividad.
Vamos a realizar la famosa validación en la que 2 contraseñas deben coincidir. Esta validación es interesante porque se deben conocer los valores de ambos campos para poder realizar la validación (por lo que se podria considerar como una validación compleja).
Al finalizar tendremos 3 componentes.
- Componente que envuelve una lightning input, donde agregamos un espacio para mostrar errores, cuya unica responsabilidad es visual.
- Componente de tipo util para mantener las validaciones que debe ser reusable en multiples componentes.
- Componente principal, el cual va a utilizar los 2 componentes anteriores. En el que se va a permitir ingresar y confirmar una contraseña y donde realizaremos la validación en la que ambos campos deben ser iguales para ser validos.
Para poder apreciar este enfoque, es bueno entender como se realizan las validaciones de forma clasica en javascript y luego podemos compararla con la forma reactiva de los LWC.
Forma Clasica Javascript
Antes de irnos de lleno con la solución, primero veamos un poco de la forma clasica, es decir accediendo al DOM, buscando el componente y realizando la validación.
Con la actual version de html (html 5) podemos utilizar algunas validaciones pre-establecidas utilizando atributos como required, pattern, min, max etc, los cuales tienen implementaciones especificas en cada navegador, por lo que no podemos garantizar una apariencia uniforme para nuestros usuarios, adicionalmente estan condicionados al envio de forms (submit) y para finalizar no podemos definir el mensaje que queremos mostrar, por lo que podemos tener el texto de nuestro formulario en español y ver los mensajes de las validaciones en chino.
Para estos problemas mencionados anteriormente podemos utilizar javascript y acceder a cada uno de los elementos en nuestra página y forzar la ejecución de las validaciones de acuerdo a nuestras necesidades, sin embargo este enfoque se sale de la filosofia de frameworks modernos como lo es LWC (al utilizar una referencia al DOM ya estamos rompiendo la reactividad).
Un corto ejemplo de como funcionaría lo que se acaba de mencionar:
Se define un simple campo de tipo email
<input type="email" id="mail" name="mail" />
Creamos un selector DOM para acceder al campo
const email = document.getElementById("mail");
Luego en el mismo javascript ejecutamos la validación en algun evento de nuestro interes:
email.addEventListener("input", (event) => {
if (email.validity.typeMismatch) {
email.setCustomValidity("I am expecting an email address!");
} else {
email.setCustomValidity("");
}
});
y finalmente obtendremos al como lo siguiente al validar
El tema de las validaciones es un poco mas extenso y pueden obtener mas información interesante en esta página: https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation
Reactividad de LWC
LWC es reactivo, esto quiere decir que en el momento que modificamos nuestro modelo, automaticamente los cambios deberian de reflejarse en nuestra vista. La ventaja de esta reactividad es que no tenemos que preocuparnos de referencias a un elemento especifico como en el ejemplo anterior con la referencia especifica de document.getElementById(“mail”). La verdad es que muy pocas veces o nunca deberiamos de referirnos a un elemento especifico del html.
Teniendo esto en cuenta vamos a desarrollar un componente LWC que permita realizar la validación sin ninguna referencia al componente de forma explicita.
Desarrollo del Componente
Al finalizar obtendremos lo siguiente:
Y cuando ingresemos texto que no sea igual y presionemos el boton validate, veremos el siguiente resultado:
El código fuente de estos componentes pueden ser encontrado aqui.
Creando el Componente Text
Este es el componente que envuelve el lightning input y define como mostrar los errores.
Código fuente
<template>
<lightning-input type={type}
label={label}
value={value}
onchange={handleChange}
class={classes}></lightning-input>
<template lwc:if={formatedErrors}>
<div class="slds-form-element slds-has-error">
<ul>
<template for:each={formatedErrors} for:item="error" >
<li class='slds-form-element__help'
key={error.index}>{error.value}</li>
</template>
</ul>
</div>
</template>
</template>
import { api, LightningElement } from 'lwc';
export default class Text extends LightningElement {
@api errors;
@api value;
@api label;
@api type;
get classes(){
if(this.errors.length > 0){
return 'slds-form-element slds-has-error';
}
return '';
};
get formatedErrors(){
return this.errors.map((value, index) =>{
return {value:value,index:index + 1}
});
}
handleChange(eventSource){
const event = new CustomEvent(
'change',
{detail:eventSource.detail}
);
this.dispatchEvent(event);
}
}
Explicación del HTML
- Lineas 3-7: agragamos el lightning-input component y envolvemos los parametros label, value, class y el evento onchange.
- Linea 9: agregamos un condicional para que muestre la sección de errores solo cuando existan errores.
- Linea 10: agregamos un div con las clases slds para mostrar los errores en el formato y color adecuado.
- Linea 12: un loop para mostrar cada uno de los errores
- Linea 13: mostramos cada mensaje con la clase slds-form-element__help para que muestre los errores correctamente.
Explicación del Javascript
- Lineas 5-9: definimos los parametros de entrada al componente para que sean pasados al lightning-input component.
- Lineas 10-15: definimos la función get classes, la cual agrega o elimina las clases slds-form-element y slds-has-error para que la caja de texto se roja o no en caso de que existan errores.
- Lienas 17-21: definimos la función get formatedErrors, la cual retorna los errores que se reciben en un arreglo simple y les agrega un index para que en loop del HTML pueda ser mostrado. El objetivo principal es evitar que los componentes padres sean simplificados al solo mandar un arreglo simple de texto con los errores.
- Lineas 23-29: se define la función handleChange, la cual es el envoltorio para el evento onchange. Esto permite que nuestro componente tenga tambien el evento onchange tal como lo tiene el lightning-input component.
Resumen del componente
Como se puede apreciar, este componente es simplemente un envoltorio del lightning-input component, el cual define como visualizar los errores que indique un componente padre, ocultando la complejida y responsabilizandose unicamente de la parte visual.
Creando el componente util
Este componente es un simple javascript con las validaciones que pueden ser reutilizadas en otros componentes.
Código fuente
export function areEqual(value1,value2){
return (value1==value2);
}
Explicación Javascript
- Linea 1-3: define la función en la que compara 2 valores y devuelve verdadero en caso de que los valores pasados por parametro sean iguales.
Resumen del componente
Como se puede apreciar este componente solo tendra la validación de comparar 2 valores.
Creando el componente aForm
Este es el componente principal el cual contiene los 2 componentes creados previamente y dara sentido al desarrollo.
Código fuente
<template>
<div class="slds-theme_default slds-box">
<div>
<c-text
label="Set a Password"
type="password"
value={value1}
errors={errorsField1}
onchange={updateValue1}></c-text>
</div>
<div>
<c-text
label="Confirm Password"
type="password"
value={value2}
errors={errorsField2}
onchange={updateValue2}></c-text>
</div>
<div class="slds-var-m-vertical_small slds-text-align_right">
<lightning-button
label="Validate"
onclick={validate}
class="slds-m-left_x-small"></lightning-button>
</div>
</div>
</template>
import { LightningElement } from 'lwc';
import { areEqual } from 'c/util';
export default class AForm extends LightningElement {
value1;
value2;
errorsField1 = [];
errorsField2 = [];
validate(){
this.errorsField1 = [];
this.errorsField2 = [];
if(!areEqual(this.value1,this.value2)){
this.errorsField1.push('Passwords do NOT match');
this.errorsField2.push('Passwords do NOT match');
}
}
updateValue1(event){
this.value1 = event.detail.value;
}
updateValue2(event){
this.value2 = event.detail.value;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>61.0</apiVersion>
<isExposed>true</isExposed>
<masterLabel>A FORM</masterLabel>
<targets>
<target>lightning__RecordPage</target>
</targets>
</LightningComponentBundle>
Explicación del HTML
- Linea 2: Se define un contenedor con las clases slds-theme_default y slds-box con el objetivo de seguir el tema por defecto de salesforce y que este en una caja con border redondeados.
- Lineas 3-10: Se define el campo de contraseña.
- Lineas 11-18: Se define el campo para confirmar la contraseña.
- Lineas 19-24: Se define un boton el cual ejecutará la validación.
Explicación del Javascript
- Linea 2: incluye la validación de util para comparar los valores de las contraseñas.
- Lineas 6-9: Se definen los atributos para controlar los valores y los errores de los campos.
- Lineas 11-20: se define la función validate, la cual se ejecutará cuando el usuario presione el boton validate.
- Lineas 13-14 (validate): se borra cualquier error previo para iniciar unas nuevas validaciones.
- Lineas 16-19 (validate): Se verifica si los valores son diferentes, en caso de que sean diferentes, se agrega en cada arreglo de errores el mensaje ‘Passwords do NOT match’.
- Lineas 22-24: se define la función updateValue1, la cual se ejecuta cada vez que cambia el valor ingresado en el campo de la contraseña y alimenta value1. El objetivo es mantener la variable actualizada con cada cambio en el texto de contraseña.
- Lineas 26-28: se define la función updateValue2, la cual se ejecuta cada vez que cambia el valor ingresado en el campo de confirmar contraseña y alimenta value2. El objetivo es mantener la variable actualizada con cada cambio en el texto de confirmar contraseña.
Expliación del XML
- Linea 4: define que el componente esta disponible en salesforce como en flexipages y flows.
- Linea 5: define el nombre con el que sera visible el componente en salesforce para las herramientas drag and drop dentro de las flexipages o flows.
- Lineas 6-8: Define que el componente va a estar disponible en cualquier record page de las flexipages de salesforce.
Conclusiones
- Se realizó un ejemplo muy básico y tal vez poco util en el sentido de que en salesforce es raro tener que validar una contraseña, pero el objetivo fue simplemente mostrar como realizar las validaciones y los componentes relacionados.
- El componente Util puede se puede extender para realizar validaciones mas generales como las basadas en expresiones regulares, o consultar el modelo de datos e incluso hasta validar con un servicio externo.
- Es bastante común tener un campo con validaciones de su propio texto basados en una expresion regular, para este caso recomendaria crear un nuevo componente que envuelva el campo texto y que valide la expresion regular. Seria un componente que acopla la validacion y el componente.
- Usualmente se piensa en validaciones acopladas en un solo componente, pero cuando hay que realizar validaciones en grupo suele ser un dolor de cabeza, precisamente el ejemplo expuesto tiene en cuenta 2 valores, pero en la vida real podemos encontrarnos con otros tipos de validaciones, algunos ejemplos podrian incluir calculos matematicos en los que la sumatoria no pase cierto valor, o que no se repita un mismo valor una cantidad especifica de veces.
- Los mensajes de error son contralados en el componente padre o principal, pero estos mensajes podiran estar cubiertos en el propio util, o en un javascript aparte e inclusive en algo mas elaborado utilizando los labels de salesforce o custom metadata.
Muchas gracias por leer.
Leave a Reply