Lo que haremos en esta ocasiÃģn serÃĄ 'jugar' con este concepto de funtor aplicativo, especÃficamente lo vamos a usar para validar datos que un usuario ha colocado en un formulario.
Si no saben quÃĐ es un funtor aplicativo tal vez quieran un breve resumen... pero no puedo hacer eso hoy. AÚn no tengo un dominio del tema lo suficientemente bueno para explicarles sin tener que bombardearlos con informaciÃģn que no necesitarÃĄn.
Si quieren saber con mÃĄs detalle les recomiendo leer por lo menos uno de estos artÃculos.
Por ahora les contarÃĐ con un ejemplo uno de los problemas que podemos resolver con un funtor aplicativo.
Imaginen
Imaginen esta situaciÃģn. Tienen un valor y una funciÃģn, y quieren aplicar la funciÃģn a ese valor.
const valor = 1;
const fn = (x) => x + 1;
La soluciÃģn es simple.
fn(valor); // => 2
Todo bien. No necesitamos ningÚn nada mÃĄs. Pero ahora imaginemos que estos valores estÃĄn "atrapados" en una estructura.
const Valor = [1];
const Fn = [(x) => x + 1];
En este ejemplo la estructura que usamos es el arreglo. Queremos aplicar la funciÃģn al valor y queremos que el resultado tambiÃĐn sea un arreglo. ÂŋCÃģmo harÃan eso?
[Fn[0](Valor[0])]; // => [2]
ÂŋAsÃ? No parece apropiado. En un mundo ideal podrÃamos hacer algo mejor.
Valor.ap(Fn); // => [2]
Lo que queremos es tratar la aplicaciÃģn de funciones cÃģmo si fuera otra propiedad (o mÃĐtodo) de nuestra estructura.
La mala noticia es que no vivimos en ese mundo. La buena noticia es que implementar esta operaciÃģn nosotros mismos.
const List = {
ap(Fn, Valor) {
return Valor.flatMap(x => Fn.map(f => f(x)));
}
};
Con esta pequeÃąa funciÃģn podrÃamos resolver nuestro problema.
const Valor = [1];
const Fn = [(x) => x + 1];
List.ap(Fn, Valor); // => [2]
El siguiente paso
Ahora pongamos nuestra atenciÃģn en otra estructura: objetos.
Imaginemos la misma situaciÃģn pero esta vez los elementos que queremos usar estÃĄn atrapados en un objeto de la misma "forma".
const Valor = {email: 'this@example.com'};
const Fn = {email: (input) => input.includes('@')};
ÂŋCÃģmo harÃamos en este caso? Bueno, tomamos la funciÃģn de una propiedad y la aplicamos al valor correspondiente en el otro objeto. Vamos a implementar esos pasos en una funciÃģn.
const Obj = {
ap(Fn, Data) {
const result = {};
for(let key in Data) {
result[key] = Fn[key](Data[key]);
}
return result;
}
}
Ahora hacemos lo mismo que en el ejemplo anterior.
const Valor = {email: 'this@example.com'};
const Fn = {email: (input) => input.includes('@')};
Obj.ap(Fn, Valor); // => {email: true}
Hagamos una cadena
Bien, podrÃamos aplicar una validaciÃģn a un campo, Âŋpero es suficiente? Probablemente no. Lo mejor serÃa devolver un mensaje de error para el usuario. Aparte de eso, tambiÃĐn serÃa una buena idea poder aplicar varias funciones a la vez.
Lo que quiero hacer es tomar una funciÃģn, un mensaje y poner ambos en un arreglo. Y quiero una lista de esos pares. Algo asÃ.
[
[long_enough, 'Intenta otra vez'],
[is_email, 'No es vÃĄlido']
]
Si la funciÃģn retorna false entonces el mensaje de error es agregado a un arreglo. Sencillo, ÂŋCierto? Vamos a crear una funciÃģn para manejar esta cadena de validaciones.
function validate(validations, input) {
const error = [];
for(let [validation, msg] of validations) {
const is_valid = validation(input);
if(!is_valid) {
error.push(msg);
}
}
return error;
}
Noten que he dejado el parÃĄmetro input de Último, esto es porque quiero "aplicar" el parÃĄmetro validations sin tener que ejecutar la funciÃģn. Para lograr este efecto usarÃĐ Function.bind.
validate.bind(null, [
[long_enough, 'Intenta otra vez'],
[is_email, 'No es un correo vÃĄlido']
]);
Hay otras formas para lograr la aplicaciÃģn parcial, pero a mi gusta esta.
Lo siguiente serÃĄ implementar las validaciones que queremos ejecutar.
function long_enough(input) {
return input.length >= 2;
}
function is_email(input) {
return input.includes("@");
}
function no_numbers(input) {
return !(/\d/.test(input));
}
Ahora podemos poner todo junto en un caso de prueba.
const input = {
name: '1',
email: 'a'
};
const validations = {
name: validate.bind(null, [
[long_enough, 'Nop. Haz un esfuerzo.'],
[no_numbers, 'ÂŋNÚmeros? No. QuÃtalos.']
]),
email: validate.bind(null, [
[long_enough, 'Intenta otra vez.'],
[is_email, 'ÂŋA quiÃĐn intentas engaÃąar?']
])
};
Obj.ap(validations, input);
Obj.ap deberÃa devolver esto.
{
name: [
"Nop. Haz un esfuerzo.",
"ÂŋNÚmeros? No. QuÃtalos."
],
email: [
"Intenta otra vez.",
"ÂŋA quiÃĐn intentas engaÃąar?"
]
}
Si quieren saber si el formulario es vÃĄlido sÃģlo tendrÃan que revisar si alguna propiedad contiene errores.
function is_valid(form_errors) {
const is_empty = msg => !msg.length;
return Object.values(form_errors).every(is_empty);
}
is_valid(Obj.ap(validations, input));
Luego de evaluar si los datos son vÃĄlidos lo que queda es mostrar los errores al usuario. Esta parte depende mucho del contexto de su programa, no puedo mostrarles un ejemplo lo suficientemente "genÃĐrico" pero sà podemos imaginar otra situaciÃģn mÃĄs especÃfica.
Formulario de registro
Vamos a suponer que tenemos un formulario html cualquiera. Cada campo tiene esta estructura.
<div class="field">
<label class="label">Nombre Campo:</label>
<div class="control">
<input name="nombre-campo" class="input" type="text">
</div>
<ul data-errors="nombre-campo"></ul>
</div>
Cuando el campo es invÃĄlido queremos mostrar la lista de errores en el elemento ul que tiene el atributo data-errors.
ÂŋCÃģmo empezamos? Primero necesitamos agregar una funciÃģn al evento submit de nuestro formulario.
function submit(event) {
event.preventDefault();
}
document.forms.namedItem("miformulario")
.addEventListener("submit", submit);
Nuestro siguiente paso serÃĄ reunir los datos del usuario. Pero en esta escenario no sÃģlo necesitamos el valor de los campos tambiÃĐn necesitamos el nombre del campo. Entonces nuestro objeto va a ser un poco mÃĄs complejo que en el ejemplo anterior.
Vamos a hacer una funciÃģn que de la informaciÃģn que necesitamos del formulario.
function collect_data(form) {
const result = {};
const formdata = new FormData(form);
for (let entry of formdata.entries()) {
result[entry[0]] = {
field: entry[0],
value: entry[1],
};
}
return result;
}
Vamos a probarla en la funciÃģn submit.
function submit(event) {
event.preventDefault();
const input = collect_data(this);
console.log(input);
}
En este punto debemos aplicar las validaciones pero la funciÃģn validate que tenÃamos no serÃĄ suficiente, necesitamos manejar un objeto y no una cadena de texto.
- function validate(validations, input) {
- const error = [];
+ function validate(validations, field) {
+ const result = {...field};
+ result.errors = [];
for(let [validation, msg] of validations) {
- const is_valid = validation(input);
+ result.is_valid = validation(field.value);
-
- if(!is_valid) {
- error.push(msg);
+ if(!result.is_valid) {
+ result.errors.push(msg);
}
}
- return error;
+ return result;
}
Hemos hecho dos cosas. Primero, sacamos el valor del input de field.value. Segundo, en lugar de un arreglo ahora devolvemos un objeto con la siguiente "forma".
{
field: String,
value: String,
is_valid: Boolean,
errors: Array
}
Hacemos esto porque es muy probable que necesitemos toda la informaciÃģn extra luego de completar el proceso de validaciÃģn.
Justo como antes, vamos a fingir que tenemos que validar nombre y correo de un usuario. Vamos a usar las mismas funciones de antes y nuestro nuevo validate.
function submit(event) {
event.preventDefault();
const input = collect_data(this);
const validations = {
name: validate.bind(null, [
[long_enough, 'Nop. Haz un esfuerzo.'],
[no_numbers, 'ÂŋNÚmeros? No. QuÃtalos.']
]),
email: validate.bind(null, [
[long_enough, 'Intenta otra vez.'],
[is_email, 'ÂŋA quiÃĐn intentas engaÃąar?']
])
};
const formdata = Obj.ap(validations, input);
console.log(formdata);
}
ÂŋPero saben quÃĐ? Quiero hacer algo gracioso. Quiero sacar validations de ahà y convertirlo en una funciÃģn usando Obj.ap.bind.
const validate_form = Obj.ap.bind(null, {
name: validate.bind(null, [
[long_enough, 'Nop. Haz un esfuerzo.'],
[no_numbers, 'ÂŋNÚmeros? No. QuÃtalos.']
]),
email: validate.bind(null, [
[long_enough, 'Intenta otra vez.'],
[is_email, 'ÂŋA quiÃĐn intentas engaÃąar?']
])
});
Ahora submit puede ser un poco mÃĄs "declarativo".
function submit(event) {
event.preventDefault();
const input = collect_data(this);
const formdata = validate_form(input);
console.log(formdata);
}
Ahora tenemos que evaluar si el formulario es vÃĄlido. Para esto sÃģlo debemos revisar si todos los campos tienen .is_valid en true. Entonces si el formulario es vÃĄlido lo que queremos hacer es enviar los datos, caso contrario debemos mostrar los errores.
function is_valid(formdata) {
return Object.values(formdata).every((field) => field.is_valid);
}
function submit(event) {
event.preventDefault();
const input = collect_data(this);
const formdata = validate_form(input);
if(is_valid(formdata)) {
send_data(input);
} else {
// mostrar errores
}
}
Para el Último paso lo que haremos serÃĄ colocar un li por cada mensaje de error que tenga nuestro input.
function show_errors(input) {
const el = document.querySelector(`[data-errors=${input.field}]`);
el.replaceChildren();
for (let msg of input.errors) {
const li = document.createElement("li");
li.textContent = msg;
el.appendChild(li);
}
}
Esperen... aÚn tenemos que encargarnos de un pequeÃąo detalle que olvidÃĐ. Un funtor aplicativo tambiÃĐn debe tener "mÃĐtodo" map, no tenemos uno pero vamos a arreglar eso.
const Obj = {
+ map(fn, data) {
+ const result = {};
+ for (let key in data) {
+ result[key] = fn(data[key]);
+ }
+
+ return result;
+ },
ap(Fn, Data) {
const result = {};
for (let key in Data) {
result[key] = Fn[key](Data[key]);
}
return result;
}
};
Ya me siento mejor. Ahora vamos a usar map para mostrar los errores.
function submit(event) {
event.preventDefault();
const input = collect_data(this);
const formdata = validate_form(input);
if(is_valid(formdata)) {
send_data(input);
} else {
Obj.map(show_errors, formdata);
}
}
Okey ya sÃĐ, map deberÃa usarse para transformar valores. No nos centremos en detalles. Vamos a alegrarnos porque ya todo el trabajo estÃĄ hecho. Aquà les dejo un formulario semi-funcional en codepen para que vean el cÃģdigo en acciÃģn.
ConclusiÃģn
Dimos un pequeÃąo vistazo a lo que pueden hacer los aplicativos con el mÃĐtodo .ap. Sabemos que en javascript no tenemos una implementaciÃģn "nativa" para esto, pero aÚn podemos hacer la nuestra. Y finalmente aplicamos este conocimiento para validar un formulario.
Gracias por su tiempo. Si este artÃculo les pareciÃģ Útil y quieren apoyar mis esfuerzos para crear mÃĄs contenido pueden dejar una propina en ko-fi.com/vonheikemen.

Top comments (0)