Damián Avila: Abandoning the oquanta domain name

This is a very short but important post!

As you probably know, this blog can be found as a subdomain of oquanta.info.

Starting Jan 23rd, 2019, I will be abandoning that domain name because GoDaddy is asking me unreasonable prices for renewal... and, actually, I am not using that domain except for the blog itself.

As you probably know as well, for several years, this blog has been hosted in gh-pages, so I will just use the default and expected URL provided by Github: http://damianavila.github.io/blog

Please, make sure to bookmark/save/link the correct URL if you want to keep reading about some of my stuff ;-)

Have a great start of the week!

Facundo Batista: Encarando el nuevo año

No soy de hacer esas "promesas de fin de año" (¿o son de año nuevo?) en las que la gente dice "este año adelgazo", "este año aprendo surf", "este año salgo del closet", etc. Pero me agarraron ganas de planificar un poco los siguientes doce meses y ponerme como grandes objetivos en los que me gustaría invertir principalmente esfuerzo, como una forma de arrancar más ordenado.

Hay una frase que leí hace poco en un texto de Forn, de Primo Levi, que indica que "Los objetivos de la vida son la mejor defensa contra la muerte". No es que a mí me falte cosas para hacer: La idea es solamente ordenar un poco el futuro a corto plazo.

A nivel de desarrollo de software me gustaría empujar fades como proyecto que lidero con alguien más, seguir puliendo mi blog, y más allá de quizás agarrar algún otro con baja prioridad, quiero lanzar este año una especie de mezcla entre grupo de tareas y grupo de aprendizaje (para newbies y diverso) alrededor de CDPedia.

No digo que no trabajaré para nada en otra cosa de software, pero seguro que mi construcción de la agenda día a día y planificación normal de semanas subsiguientes va a estar fuertemente condicionados por estos proyectos que me "anoté".

Do I really look like a guy with a plan?

A nivel Python Argentina lo tengo separado como dos partes. Una es el grupo en sí mismo, y lo que tengo ganas de empujar este año es por un lado la migración conceptual a Discourse (conceptual, digo, porque no vamos a "migrar" realmente, es una nueva herramienta, que apunto a que termine reemplazando la vieja lista de mailman), la creación de un nuevo logo (cumplimos 15 años, es hora, ¿no?) y empujar a que Eventol gane características para ser usado como plataforma de juntadas recurrentes (como meetup.com, pero sin lock-in ni límite de asistentes) para la coordinación de juntadas en todo el país.

La otra es el laburo en la Asociación Civil. Estoy metiéndole detalles y detalles a medida que lo uso al sistema de Socies, y esperando que vuelva Gilga de vacaciones para ponerlo online: mi idea a este respecto es seguir agregándole features y pequeñas mejoras pero muy puntuales en función de que vaya surgiendo las distintas necesidades. Por otro lado, necesitamos un sistema para Soporte de Eventos (que básicamente maneje todo lo que es entrada y salida de dinero alrededor de un evento nuestro o de terceros), pero la idea ahí es armar una definición y que lo haga alguien más.

A nivel de evento propiamente dicho, por otro lado, tengo en vista dos o tres. Dos seguro, que es el PyCamp 2019, el cual ya pagué y compré pasajes (lo organiza Matu Varela, con el soporte de la Asociación Civil, claro), y la PyCon Argentina 2019, que todavía tenemos que disparar la organización propiamente dicha. Digo "dos o tres" porque tenía ganas de ir a la PyCon USA, pero justo cae durante mi cumpleaños, entonces me frenó un poco eso... para colmo me di cuenta de eso luego que propuse algunas charlas, entonces tomé la decisión de que si me aprueban alguna charla voy, pero si no aprovecho y me quedo con mi familia :)

Mariano Guerra: Creemos en la Web: Datos por favor, es promesa

En el capítulo anterior vimos como cargar datos simulando la espera que surge de cargar datos de servicios remotos, ahora vamos a ver como cargar datos remotos de verdad, pero primero tenemos que aprender sobre algo llamado promesas.

En una aplicación web todas las partes del código tienen que colaborar haciendo su trabajo lo mas rápido posible y dejando que otros puedan hacer su trabajo, si un pedazo de código se toma mucho tiempo, otras partes importantes no se ejecutan y se produce algo que te puede haber pasado, que es que la aplicación "se congela".

Es por eso que muchas funcionalidades en js se descomponen en pedazos mas chicos para evitar este problema.

Una de ellas es cargar datos de otros servicios, no podemos darnos el lujo de esperar hasta que respondan, ya que si se toma un par de segundos la aplicación se congela.

La solución es hacer la solicitud y obtener como resultado una promesa.

Una promesa es un objeto que nos permite registrar funciones para cuando la promesa sea cumplida. La promesa puede ser cumplida exitosamente o puede haber un error. También podemos registrar funciones para que corran cuando la promesa sea cumplida, no importa si con éxito o con error.

El tipo de dato promesa (Promise en ingles), no es nada mágico, si no que esta disponible para que la usemos si la necesitamos, vamos a probarla con ejemplos simples, no te preocupes si no entendés la parte de crear promesas, requiere un poco de "pensar de adentro para afuera", normalmente al principio solo "consumimos" promesas, pero es necesario que las creemos así podemos probar todos los casos.

Proba los fragmentos de código en glitch o en la consola en las herramientas de desarrollo.

Una promesa nunca cumplida:

let p1 = new Promise(function (resolve, reject) {});

Esta es la forma mas simple de crear una promesa, el tipo de dato Promise permite crear nuevos objetos de ese tipo con el operador new, el "constructor" de las promesas recibe como argumento una función, la función va a ser llamada en el momento en el que la promesa es construida y dicha función recibe dos argumentos:

resolve
una función a ser llamada si la promesa se cumple con éxito.
reject
una función a ser llamada si la promesa se cumple con un error.

Tanto resolve como reject son funciones que reciben un solo argumento, que va a ser pasado a las funciones que registremos para ambos casos.

Una promesa resuelta instantaneamente con éxito:

let p2 = new Promise(function (resolve, reject) {
    resolve('éxito!');
});

Una promesa resuelta instantaneamente con error:

let p3 = new Promise(function (resolve, reject) {
    reject('error!');
});

Todo muy lindo, pero para que sirve que la resolvamos con éxito o error si no cambia nada?

La utilidad de las promesas es poder registrar una o mas funciones que van a ser llamada en los tres casos que ya mencionamos: éxito, error o cuando se resuelva no importa el caso.

Empecemos registrando una función con la promesa que nunca se resuelve, vamos a usar el método then (entonces en ingles) de los objetos de tipo Promise para registrar una función que va a ser llamada cuando la promesa se cumpla exitosamente, cuando sea llamada va a recibir un argumento que es el valor con el que la promesa se cumplió (el valor prometido ;).

p1.then(function (resultado) {
    alert('p1 resuelta: ' + resultado);
});

A mi en firefox me muestra este resultado, que quiere decir que llamar al método then del objeto promesa devuelve la promesa misma, esto nos va a ser útil después para "encadenar" llamadas a métodos en la promesa.

< Promise { <state>: "pending" }

Fuera de eso, all llamar al método then del objeto promesa p1 no paso nada, es natural ya que esa promesa esta "pendiente" (pending en ingles) y nunca se va a cumplir.

La promesa p2 se cumplió apenas la creamos, que pasa si registramos una función a ser llamada cuando se resuelva con éxito? Probemos:

p2.then(function (resultado) {
    alert('p2 resuelta: ' + resultado);
});

Una aclaración, las funciones que pasamos por parámetro para que sean llamadas en ingles se llaman "callbacks", que traducido es "llamame de vuelta", probablemente use esa palabra de ahora en mas porque es mas corta y para que te acostumbres ya que se usa mucho en la documentación.

Volviendo al código, si lo probaste habrás notado que aparece un cuadro de dialogo mostrando el mensaje "p2 resuelta: éxito!", es decir que si registramos un callback aun después de que la promesa sea cumplida la función va a ser llamada.

Corre el código de nuevo y vas a ver que el dialogo aparece de nuevo, esto es útil ya que no tenemos que preocuparnos si registramos el callback antes o después de que la promesa se resuelva, una cosa a tener en cuenta es que cada callback va a ser llamada una sola vez, ya que cada objeto de promesa puede ser resuelta una vez.

Ahora probemos lo mismo con la promesa que se resuelve con error:

p3.then(function (resultado) {
    alert('p3 resuelta: ' + resultado);
});

El dialogo no aparece... porque then registra callbacks para el caso de éxito, si queremos registrar callbacks para el caso de error, tenemos que usar el método llamado catch (capturar en ingles).

p3.catch(function (resultado) {
    alert('p3 error: ' + resultado);
});

Ahora el dialogo aparece.

Que pasa si queremos hacer algo en ambos casos? hay otro método llamado finally (finalmente en ingles).

p2.finally(function () {
    alert('p2 finally');
});

p3.finally(function () {
    alert('p3 finally');
});

Ambos muestran el dialogo, pero como veras no reciben el valor de resolución porque no sabemos cual sucedió.

Que pasa si queremos hacer un poco de todo, algo si salio bien, por ejemplo actualizar datos, algo si salio mal, por ejemplo mostrar un error y algo siempre, por ejemplo esconder un mensaje de "Cargando".

Obviamente podemos escribir las tres llamadas separadas, pero como mencione las llamadas a los métodos then, catch, finally devuelven la promesa, por lo que podemos hacer algo que se llama "encadenar" llamadas, veamos como es:

p2.then(function (resultado) {
    alert('then: ' + resultado);
})
.catch(function (resultado) {
    alert('catch: ' + resultado);
})
.finally(function () {
    alert('finally');
});

No cambia nada con hacerlo junto o por separado, pero suele hacerse según preferencia así que lo aclaro.

Bueno, basta de promesas (cuac!), veamos como usar esto para cargar datos, para eso vamos a usar una función llamada fetch que hace una solicitud HTTP (como tu navegador para cargar esta pagina, imágenes y datos) y devuelve una promesa, vamos a cargar datos de ejemplo que puse en una pagina:

let url = "https://marianoguerra.github.io/creemos-en-la-web/paginas/promesas/datos.json";
fetch(url);

Ahora con todo lo que sabemos sobre promesas, veamos que nos da la promesa:

fetch(url).then(function (response) {
    console.log(response);
});

Lo que hago es mostrar el valor de response en la consola usando el método log del objeto console (consola en ingles).

A mi en firefox me muestra esto, a vos te puede mostrar algo un poco distinto:

Response { type: "cors", url: "https://marianoguerra.github.io/creemos-en-la-web/paginas/promesas/datos.json", redirected: false, status: 200, ok: true, statusText: "OK", headers: Headers, body: ReadableStream, bodyUsed: false }

Es un objeto de tipo Response (respuesta en ingles) que tiene información variada sobre la solicitud que hicimos, pero lo que nosotros lo que queremos son los datos cuando la solicitud termine, para eso tenemos que pedirle al objeto response que lea el contenido de la respuesta. El objeto response tiene muchos métodos, uno de ellos es el método text, que nos devuelve... otra promesa..., la cual al resolverse nos da el contenido de la solicitud.

fetch(url).then(function (response) {
    response.text().then(function (text) {
        console.log('Texto!', text);
    });
});

Al correrlo debería mostrar lo siguiente en la consola:

Texto! {
    "numero": 42,
    "lista": [1, 2, 3]
}

Como veras el contenido es texto, pero notaras que son datos javascript, el subset de javascript que sirve para describir datos y enviarlos entre computadoras se llama JSON (pronunciado yeison, acrónimo en ingles de JavaScript Object Notation, que significa Notación de Objetos JavaScript).

Hay un objeto llamado JSON que tiene dos métodos útiles, uno llamado parse que recibe como argumento un valor de tipo texto (string) y nos devuelva los datos representados en ese texto, este es el que necesitamos, probemoslo:

fetch(url).then(function (response) {
    response.text().then(function (text) {
        let datos = JSON.parse(text);
        console.log('Datos!', datos);
    });
});

Por suerte como esta es una actividad común, el objeto response tiene un método llamado json que hace la tarea por nosotros:

fetch(url).then(function (response) {
    response.json().then(function (datos) {
        console.log('Datos!', datos);
    });
});

Ya que estamos hablando de JSON veamos el otro método stringify (algo así como "hacer texto" en ingles), que es el inverso de parse, es decir, recibe datos y nos devuelve la representación JSON de esos datos en un valor de tipo texto (string):

JSON.stringify({numero: 42, lista: [1, 2, 3]});

El resultado es:

< "{\"numero\":42,\"lista\":[1,2,3]}"

Para estar seguros de que funciona, probemos el inverso:

JSON.parse("{\"numero\":42,\"lista\":[1,2,3]}");

Notar que para poder insertar comillas dobles en un string, que ya tiene comillas dobles para indicar comienzo y fin, necesitamos poner una barra invertida antes de la comilla, para indicarle que no es el fin del string, sino que queremos poner esa comilla "dentro" del string. Esto normalmente se llama "escapar" caracteres.

Para finalizar, solo recordar que como fetch devuelve una promesa, podemos "encadenar" llamadas a then, catch y finally para hacer distintas operaciones según cual fue el resultado de la solicitud.

La forma general es:

fetch(url).then(function (response) {
    // resultado de la solicitud
})
.catch(function (error) {
    // si hubo error
})
.finally(function (error) {
    // cuando la solicitud termino
});

Resumiendo, aprendimos sobre promesas, llamadas encadenadas, la función fetch para hacer solicitudes a otros servicios, el formato JSON y su objeto con sus métodos parse y stringify.

Facundo Batista: A veces no es tan fácil jugar

Intro 1

En este post les había comentado de este HW que yo usado para montar una nube en casa, no sólo para mi servicio de sincronización de archivos, sino también para generar CDPedias.

Bueno, hace un año o más o menos, se rompió. Empecé a notar que a veces la máquina estaba colgada, y la reiniciaba. Luego me pasó que a veces no levantaba del reset, cada tanto. Y más seguido. Y llegó un punto en que no booteó más :(.

A la hora de intentar arreglarlo me enfrenté con la triste realidad que los componentes no eran estándar para nada. Entonces, ¿cómo sabía qué se había roto? Seguro era la CPU, la mother, o la memoria... pero para ver qué era lo roto tenía que empezar a comprar componentes, quizás en falso. No podía probar componentes de otra computadora que sí sabía que andaban.

Entre una cosa y la otra, un amigo me recomendó poner una "mini PC", que no es más que una computadora PC normal pero pensada para que ocupe poco espacio, y que consuma poco, incluso sacrificando rendimiento, para no tener un ventilador en la CPU y generar menos ruido. Pero todo con componentes "normales" (especialmente la memoria, que es algo que se jode a veces y es trivial encontrar otra y probar). Me terminé comprando esto:

La Biostar A68N-5100

Intro 2

En otro orden de cosas, con Felu hace rato que estamos jugando aventuras gráficas. Él se enganchó con Thimbleweed Park y desde ahí no paramos: las tres Monkey Island, Indiana Jones, Gabriel Knight, Sam y Max, Día del Tentáculo...

...y se nos empezaron a acabar los que corrían más o menos fácil en ScummVM o Dosbox. Se me ocurrió jugar al Myst.

El problema es que el Myst es un juego "de Windows", y conseguir software viejo en Windows es siempre un quilombo. Yo había comprado el juego, unos ¿20? años atrás, pero claro, andá a saber donde están esos discos, posiblemente en el CEAMSE.

¿Entonces?

El siguiente es el relato de cómo armé un setup para jugar al Myst desde la compu que uso normalmente (la de escritorio, que corre KDE Neon sobre Ubuntu Bionic).

Vamos con la aventura de jugar al Myst

Primera imagen (súper representativa) del Myst

Localmente

Lo primero era conseguir un Windows corriendo. Así que bajé el último Virtualbox y lo instalé, luego bajé un Windows legal pensado para armar VMs, y armé una VM con eso (Virtualbox te permite "importar" las definiciones que eso mismo que bajás trae, es casi demasiado fácil).

¡Paréntesis! En todo este artículo uso mucho la sigla "VM": significa Virtual Machine, "máquina virtual" en castellano, simplemente la posibilidad de simular una computadora completa por software (miren Wikipedia para más info). Cierro paréntesis.

Levanté la VM, instalé Steam, donde compré Myst a $78 (Masterpiece edition, que es el original con algunos detalles, no confundir con uno super remasterizado 3D, que no me interesaba, porque quería el original)... sí, 78 pesos.

Lo ejecuté, y aunque funcionaba, se escuchaba muy mal. No con ruido, sino como entrecortado. Estuve revisando y buceando la internechi hasta que encontré que parece que es un problema con la versión de Virtualbox que estoy usando, que es la 5.2. Lo comprobé metiendo un mp3 en la VM y reproduciéndolo ahí: se escuchaba igual de entrecortado, con lo cual descarté que el problema fuese de Steam o Myst.

Por lo que leí parece que es un problema de buffer circular en la simulación de la placa de sonido, donde el guest escribe y el host lee, y hay un mismatch de velocidad de lectura/escritura y todo bien mientras los punteros están en lugares distintos, pero cuando se cruzan, se entrecorta.

En fin, era una porquería.

También encontré que todos decían que la última versión de Virtualbox donde había funcionado bien era la 5.0.40. Fui a bajar esa, pero sólo la sacaron hasta Ubuntu Xenial. O sea, no hay Virtualbox 5.0.40 para la versión de mi sistema operativo, que es más nuevo.

Ahí se me prendió la lamparita: yo tengo un server con Xenial. ¿Se podrá instalar Virtualbox en un servidor, correr un Windows adentro, con Steam, y usarlo desde mi escritorio?

Descubriendo un mundo de posibilidades

Remotamente

Bajé el Virtualbox 5.0.40 y su paquete de extensión y los llevé al server, e instalé:

sudo dpkg -i virtualbox-5.0_5.0.40-115130~Ubuntu~xenial_amd64.deb
sudo VBoxManage extpack install Oracle_VM_VirtualBox_Extension_Pack-5.0.40-115130.vbox-extpack

Luego armé la VM con Windows, y la levanté:

VBoxManage import IE11\ -\ Win7.ovf
VBoxHeadless --startvm "IE11 - Win7"

Para configurarle cosas en general tiene que estar apagada. Pueden simular "apretar el botón de apagado" o directamente "desenchufarla a lo bruto", con los siguientes comandos:

VBoxManage controlvm "IE11 - Win7" acpipowerbutton
VBoxManage controlvm "IE11 - Win7" poweroff

Para usar visualmente la VM, la idea era conectarme remotamente desde mi computadora de escritorio. Para eso hay que habilitar el "escritorio virtual remoto", y agregarle una autenticación por usuario/clave:

VBoxManage modifyvm "IE11 - Win7" --vrde on
VBoxManage setproperty vrdeauthlibrary "VBoxAuthSimple"
VBoxManage modifyvm "IE11 - Win7" --vrdeauthtype external
VBoxManage internalcommands passwordhash LA-CLAVE-QUE-QUIERAN
VBoxManage setextradata "IE11 - Win7" "VBoxAuthSimple/users/EL-USUARIO-QUE-QUIERAN" EL-HASH-QUE-DIO-EL-COMANDO-ANTERIOR

Para que me ande el audio tuve que anularle la deshabilitación por default:

VBoxManage modifyvm "IE11 - Win7" --vrdeproperty Client/DisableAudio=

La comunicación del portapapeles no la pude hacer andar, pero sí el que monte un directorio compartido (instalando primero los agregados en el guest):

VBoxManage storageattach "IE11 - Win7" --storagectl "IDE Controller" --port 1 --device 0 --type dvddrive --medium /usr/share/virtualbox/VBoxGuestAdditions.iso
VBoxManage sharedfolder add "IE11 - Win7" --name shared --hostpath /home/facundo/vbox/shared --automount

No voy a contar el detalle de todos lo que probé y no me anduvo, pero les dejo un par de tips:

  • en algunos casos estos comandos de "cambiar cosas de la VM" dan errores super raros: probá apagando la VM y mandando el mismo comando con la VM apagada (tiene sentido, pero el error original no tiene nada que ver y confunde)

  • estos comandos son piolas para ver las VMs todas, las que tenés corriendo, e info puntual de alguna:

    VBoxManage listvm
    VBoxManage list runningvms
    VBoxManage showvminfo "IE11 - Win7"
    
  • toda la info sobre virtualbox remoto acá.

Fantástico. Ahora puedo levantar el krdc en mi máquina de escritorio, me conecto al Virtualbox de mi server, y tengo una ventana/pantalla con Windows andando. Llevé un mp3, lo reproduje, y escuchaba el audio perfectamente, sin problema alguno.

¡Buenísimo! Instalé Steam, perfecto. Instalé Myst (al loguearme con mi misma cuenta, ya lo tenía comprado).

Ejecuté Myst. Crasheó. Estuve probando algunas cosas (como agregarle aceleración de video 2D, seguía crasheando, o agregarle aceleración 3D, que no me dejaba porque no tenía sistema gráfico asociado).

Tristeza não tem fim.

Todo mal, loco

¿Y ahora? ¿Qué alternativas tenía?

Ahí me cayó la ficha que "mi server en la nube" está realmente a un metro de distancia, y que si le enchufaba un teclado y un mouse podría probar esto mismo pero pseudolocalmente.

Local, pero en la nube (?)

Busqué un cable HDMI (que conecté al mismo monitor que uso con la compu de escritorio, que está conectada por DVI), un mouse viejo, y le robé el teclado a la raspi que tengo para jugar. Enchufé todo, entré, pero tenía todas terminales como sólo texto, no tenía una interfaz gráfica. ¡Claro! Nunca había instalado un escritorio, ya que era una máquina servidora pura. Ergo:

sudo apt install ubuntu-desktop
sudo systemctl enable lightdm
sudo reboot

Ahora sí: inicié sesión en el Ubuntu, abrí Virtualbox, todo de forma gráfica. Entré en Steam. Corrí Myst. Crasheó. :(

Ví que podía hacer en la configuración. Probé lo más fácil: subirle la memoria a la placa de video, y agregarle aceleración 3D (ahora sí estaba en un entorno gráfico, je).

Levanté todo. Probé Myst. ¡Anduvo! Claro, sin sonido porque la máquina server no tiene parlantes enchufados.

Pero volví a la computadora de escritorio (que es decirle al monitor que use la otra entrada), levanté el krdc de nuevo, me conecté al server, donde obviamente ya tenía levantado todo, y finalmente pudimos jugar al Myst.

Funciona un poco lento, porque la computadorita server no es gran cosa (está más pensada para que consuma poco que para que uno corra juegos), y encima tiene al Virtualbox simulando una máquina para que corra Windows, para poder ejecutar Steam donde adentro corre el juego en sí.

Adentro de adentro de adentro de adentro de adentro de

Pero podemos jugar al Myst :D

Feliz año.

Mariano Guerra: Creemos en la Web: Datos por favor, pero después

En el capítulo anterior vimos como pedirle datos al usuario, pero en el momento de validar los datos, lo hicimos simple con lógica fija en el código.

En una aplicación real, le preguntaríamos a un servicio que tiene registradas las cuentas si el usuario y la contraseña son validas.

Este tipo de servicios suelen llamarse APIs (interfaz de programación de aplicaciones en ingles).

Las APIs son como funciones pero que corren en otras computadoras, razón por la cual al llamarlas no devuelven instantáneamente si no que demoran un buen rato, así que tenemos que aprender a esperar y manejar distintos casos.

Por ejemplo cuando la solicitud funciona correctamente y cuando falla, ya sea por problemas de red, porque mandamos parámetros inválidos, porque el servicio no esta funcionando correctamente o una gran variedad de causas normalmente resumidas con "se cayo el sistema".

Empecemos cargando datos y mostrandolos, arrancamos con todo el HTML ya que no hay nada nuevo:

<!doctype html>
<html>
  <head>
    <!-- para que muestre bien tildes y caracteres especiales -->
    <meta charset="utf-8">

    <!-- para que se vea bien en Internet Explorer -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <!-- para que se vea bien en telefonos mobiles -->
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Cargando Datos</title>

    <!-- importamos vue para poder crear aplicaciones con javascript https://vuejs.org/ -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
    <!-- importamos las hojas de estilo base bootstrap https://getbootstrap.com/ -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">

    <!-- importamos nuestro estilo especifico -->
    <link rel="stylesheet" href="/style.css">

    <!-- importamos nuestro javascript -->
    <script src="/client.js" defer></script>
  </head>
  <body class="m-3">

    <!-- la raíz de nuestra aplicación -->
    <div id="app">
      <div v-if="cargando">
        <div class="alert alert-info">
          Cargando
        </div>
      </div>
      <div v-else="">
        <table class="table table-bordered table-stripped table-hover">
          <tbody>
            <tr><td>Fecha</td><td>{{datos.fecha}}</td></tr>
            <tr><td>Numero</td><td>{{datos.numero}}</td></tr>
            <tr><td>Nombre</td><td>{{datos.nombre}}</td></tr>
          </tbody>
        </table>

        <div class="text-center">
          <div @click="recargar()" class="btn btn-primary">
            Recargar
          </div>
        </div>
      </div>
    </div>

  </body>
</html>

Notaras que hice copy/paste del capitulo anterior, lo único que cambio es lo que esta dentro del div app, pero tampoco es algo nuevo, veamoslo en detalle:

El primer div solo se muestra si el atributo cargando es true, mostrando el mensaje "Cargando".

<div v-if="cargando">
  <div class="alert alert-info">
    Cargando
  </div>
</div>

Sino (con v-else que es como el else del if en javascript) significa que ya cargamos los datos, osea que los podemos mostrar, asumimos que los datos tienen al menos 3 campos: fecha, numero y nombre.

<div v-else="">
  <table class="table table-bordered table-stripped table-hover">
    <tbody>
      <tr><td>Fecha</td><td>{{datos.fecha}}</td></tr>
      <tr><td>Numero</td><td>{{datos.numero}}</td></tr>
      <tr><td>Nombre</td><td>{{datos.nombre}}</td></tr>
    </tbody>
  </table>

Finalmente ponemos un botón que permite recargar los datos.

<div class="text-center">
  <div @click="recargar()" class="btn btn-primary">
    Recargar
  </div>
</div>

La lógica de nuestra aplicación esta en el archivo client.js, empecemos con una base que ya conocemos:

/*globals Vue*/

function main() {
  let app = new Vue({
    el: '#app',
    methods: {
      recargar: function () {
        // cargar aca
      }
    },
    data: {
      cargando: true,
      datos: {
      }
    }
  });

  app.recargar();
}

window.addEventListener('load', main);

El código no hace nada nuevo, y de hecho no esta terminado, pero repasemos.

window.addEventListener('load', main);

Cuando la pagina termine de cargar llamamos a la función main.

La función main crea un nuevo objeto Vue, que va a funcionar en el tag con id "app", la aplicacion tiene dos atributos:

cargando
indica si la aplicación esta cargando datos
datos
los datos cargados, inicialmente vacíos

Nuestra aplicación también tiene un método, recargar, que por ahora no hace nada, osea que si probas esto la pagina se va a quedar en "Cargando" por siempre.

En el método recargar podríamos hacer algo como:

recargar: function () {
  this.datos = {nombre: "bob", numero: 42, fecha: "una fecha"};
  this.cargando = false;
}

Pero eso setearia datos instantáneamente, nunca veríamos el mensaje "Cargando", si solo hubiera una forma de pedirle al navegador que llame a una función "después"...

Obviamente dicha funcionalidad existe y suele ser muy útil, por ejemplo para mostrar o esconder mensajes después de un tiempo prudente.

Esta funcionalidad esta disponible en el objeto window y se llama setTimeout, el método recibe dos argumentos, el primero es la función a llamar, el segundo es cuanto tiene que pasar entre el momento en el que llamamos al método y cuando se va a llamar a nuestra función, en milisegundos (1 segundo son 1000 milisegundos).

recargar: function () {
  let app = this;

  function cargarFalso() {
      app.datos = {nombre: "bob", numero: 42, fecha: "una fecha"};
      app.cargando = false;
  }

  app.cargando = true;

  window.setTimeout(cargarFalso, 3000);
}

Hay un par de detalles importantes en ese código, vamos por partes:

Primero creamos una variable llamada app que tiene el mismo valor que this, porque haríamos eso? lo vamos a ver en un par de lineas.

let app = this;

Luego creamos una función llamada cargarFalso, que se llama así porque va a simular que estamos cargando los datos de un servicio esperando un poco y seteando los datos.

Lo primero para notar es que podemos crear una función dentro de otra función o método, esto lo hacemos acá por dos razones.

La primera razón es porque a esta función solo la necesitamos en el método recargar, así que hacerla "visible" al resto del código es innecesario y puede llegar a hacernos pensar después que es una función que tiene algún tipo de utilidad general cuando en realidad no la tiene fuera del método recargar.

La segunda es que queremos tener acceso a la variable app que esta disponible en los métodos de nuestra aplicación vue, las funciones "ven" las variables declaradas en su "cuerpo" (body en ingles, lo que esta entre llaves), entonces al declararla dentro del método recargar tenemos acceso a la variable sin necesidad de hacer cosas raras, ya que window.setTimeout llama a una función pero no le pasa ningún argumento.

En el cuerpo de la función cargarFalso podemos ver que seteamos el atributo datos de la variable app y aquí revelamos el misterio de la linea let app = this;, como vimos en el capitulo anterior, cada variable tiene un this implícito, su valor depende del "valor antes del punto" al llamarla, osea que cada función puede tener un valor distinto de this o ninguno si llamamos a la función directamente y no como un método.

Para asegurarnos de setear datos en el objeto adecuado, asignamos el valor de this en el método de nuestra aplicación a otra variable para que no se confunda con el this de la función cargarFalso.

No te preocupes, esta es una de las cosas mas confusas de javascript y te puede llevar una buena cantidad de tropiezos terminar de entenderlo por completo.

function cargarFalso() {
    app.datos = {nombre: "bob", numero: 42, fecha: "una fecha"};
    app.cargando = false;
}

Seteamos el atributo cargando de nuestra aplicación a true así la pagina cambia y muestra el mensaje "Cargando" hasta que la función cargarFalso sea llamada.

app.cargando = true;

Finalmente llamamos al método setTimeout del objeto window y le pasamos nuestra función a llamar y cuando la tiene que llamar (en 3000 milisegundos, es decir 3 segundos).

window.setTimeout(cargarFalso, 3000);

Si probas esto vas a ver que la pagina va a cargar, mostrar el mensaje "Cargando" y 3 segundos después va a mostrar los datos.

Si apretamos el botón "Recargar" va a mostrar el mensaje y 3 segundos después los datos.

Hagamos una pequeña modificación para que el cambio se note al hacer click en "Recargar" hagamos que el valor del campo fecha cambie.

Para obtener la fecha y hora actual existe un tipo de objeto llamado "Date" (fecha en ingles).

Cuando creamos un nuevo objeto del tipo Date el objeto que nos devuelve contiene la fecha y hora actual según el reloj de tu computadora, probemos un poco:

Creamos un nuevo objeto de tipo Date usando new:

let date1 = new Date();

Llamamos al método toString que devuelve una representación de la fecha como texto:

date1.toString();
< 'Tue Jan 01 2019 17:41:01 GMT-0300 (-03)'

Llamamos al método toLocaleString que devuelve una representación de la fecha como texto adaptada al idioma y configuración de tu computadora, en tu caso probablemente aparezca distinto al mio, que tengo un lio de configuraciones de ingles, español y alemán:

date1.toLocaleString();
< '2019-1-1 17:41:01'

El método toLocaleDateString devuelve una representación de la fecha, sin la parte del tiempo, como texto adaptada al idioma y configuración de tu computadora:

date1.toLocaleDateString();
< '2019-1-1'

El método toLocaleTimeString devuelve una representación de la fecha, solo la parte del tiempo, como texto adaptada al idioma y configuración de tu computadora:

date1.toLocaleTimeString();
< '17:41:01'

El método getDate Devuelve el día del mes.

No confundirlo con getDay que devuelve el día de la semana.

date1.getDate();
< 1

El método getMonth devuelve el numero de mes, para complicarnos la existencia enero es 0 en lugar de 1:

date1.getMonth();
< 0

El método getFullYear devuelve el año.

No confundirlo con getYear que devuelve el año menos 1900, osea que 2019 es 119`, si, no tiene sentido, como muchas cosas que vamos a aprender.

date1.getFullYear();
< 2019

El método getHours devuelve la hora.

date1.getHours();
< 17

El método getMinutes devuelve los minutos.

date1.getMinutes();
< 41

El método getSeconds devuelve los segundos.

date1.getSeconds();
< 1

Esos son los principales métodos, y luego de este breve tour alternativo volvemos a lo que nos compete, usar new Date() para mostrar la hora del momento en el que cargaron los datos:

Cambiamos cargarFalso para que en el campo fecha tenga la fecha actual en texto:

function cargarFalso() {
    let fecha = new Date(),
        fechaTexto = fecha.toLocaleString();

    app.datos = {nombre: "bob", numero: 42, fecha: fechaTexto};
    app.cargando = false;
}

Ahora al apretar "Recargar" la fecha debería cambiar indicandonos que cargo de nuevo.

Ya aprendimos mucho, como "llamar funciones después", como declarar funciones dentro de otras para tener acceso a las variables del cuerpo de la función, como no confundir el this de distintas funciones creando variables y sobre el tipo de dato Date y alguno de sus métodos, pero lo mejor esta por llegar, en la próxima vamos a usar todo esto que aprendimos para cargar los datos de un servicio/servidor/API.

El resultado esta en https://marianoguerra.github.io/creemos-en-la-web/paginas/datos-despues/2/

Podes explorar el código con las herramientas de desarrollo.

Mariano Guerra: Creemos en la Web: Datos por favor, pero validos

Ya sabemos suficiente HTML, CSS y Javascript para hacer una aplicación completa, así que empecemos y aprendamos lo que falta a medida que lo vayamos necesitando.

Gran parte de las aplicaciones implica mostrar y solicitar datos, así que probemos solicitar datos, mas conocidos como "formularios" o "forms" para los amigos.

El primero que nos vamos a encontrar en cualquier aplicación es el de autenticación, donde pedimos usuario y contraseña y lo validamos, si es valido pasamos a la siguiente pagina, si no, mostramos un mensaje de error.

Empecemos con un "esqueleto de aplicación vue", es decir, los componentes necesarios pero sin que haga algo en particular.

Para eso necesitamos un archivo html, llamemoslo index.html:

<!doctype html>
<html>
  <head>
    <!-- para que muestre bien tildes y caracteres especiales -->
    <meta charset="utf-8">

    <!-- para que se vea bien en Internet Explorer -->
    <meta http-equiv="X-UA-Compatible" content="IE=edge">

    <!-- para que se vea bien en telefonos mobiles -->
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Login</title>

    <!-- importamos vue para poder crear aplicaciones con javascript https://vuejs.org/ -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
    <!-- importamos las hojas de estilo base bootstrap https://getbootstrap.com/ -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">

    <!-- importamos nuestro estilo especifico -->
    <link rel="stylesheet" href="/style.css">

    <!-- importamos nuestro javascript -->
    <script src="/script.js" defer></script>
  </head>
  <body class="m-3">

    <!-- la raíz de nuestra aplicación -->
    <div id="app">
      <h1>Soy una aplicación vue.js basica</h1>
    </div>

  </body>
</html>

Si no te acordás algunos de los elementos podes repasarlos de capítulos anteriores o enterarte que yo no los escribí de memoria sino que los fui a buscar a un capitulo anterior y lo copie acá ;)

Para darle "vida" a esta aplicación vamos a necesitar un poco de Javascript, indicado por la presencia del tag <script src="/script.js"></script> en el html.

Ahora creemos un archivo llamado script.js:

/*globals Vue*/

function main(event) {
  let app = new Vue({
    el: '#app',
    data: {}
  });
}

window.addEventListener('load', main);

El código define una función llamada main (principal en ingles) que va a ser el "punto de entrada" de nuestra aplicación.

Pero cuando la llamamos?

Necesitamos llamarla cuando la pagina termine de cargar, para saber eso le podemos decir al objeto window que cuando termine de cargar llame a la función main.

En HTML/Javascript distintos elementos pueden emitir eventos para notificar de cosas que suceden, uno de ellos es la ventana que nos puede avisar cuando termino de cargar.

Para poder reaccionar a estos eventos, tenemos que "escuchar" (listen en ingles).

Los objetos que emiten eventos tienen un método llamado addEventListener (recordemos que un método es una función asociada a un objeto que podemos llamar con objeto.metodo()).

El método addEventListener ("añadir oyente de evento" en ingles) recibe dos argumentos, el primero es el nombre del evento que queremos escuchar, en este caso es el evento 'load'. El segundo argumento es la función a llamar cuando ese evento suceda. La función va a ser llamada con un argumento que contiene información sobre el evento que acaba de suceder, en nuestro caso no nos hace falta.

Cuando la pagina cargue, el objeto window (ventana) va a emitir el evento 'load' y va a buscar todas las funciones que escuchan ese evento y las va a llamar pasandoles un objeto evento con la información del evento.

En ese momento nuestra función main sera llamada, la cual va a crear un nuevo objeto de tipo Vue pasandole opciones de inicialización en un objeto, las opciones de inicialización por ahora son:

el
id del tag donde la aplicación va a correr, en nuestro caso es el id app, para indicarle que es un id le ponemos el # adelante (después vamos a ver otras formas de indicarle el tag raiz.
data
los datos iniciales de nuestra aplicación, por ahora un objeto vacio.

Por si te dio curiosidad que es el /*globals Vue*/, es para indicarle al programa que chequea errores que la variable Vue existe porque otro script la definió, así no nos dice que estamos refiriéndonos a una variable inexistente.

Acá tenes el proyecto base en glitch.com, hace click en Remix to Edit arriba a la derecha para hacerle cambios, hace click en Edit abajo a la derecha para abrir este ejemplo en una pagina completa para editarlo.

Abajo a la izquierda podes cambiar entre ver el código (Code), la aplicación (App) o ambas (Both).

Muy lindo todo pero esta aplicación no hace nada, empecemos por agregar los campos de usuario y contraseña, para eso los definimos en el estado inicial de nuestra aplicación (el campo data):

/*globals Vue*/

function main(event) {
  let app = new Vue({
    el: '#app',
    data: {
        username: '',
        password: ''
    }
  });
}

window.addEventListener('load', main);

Luego, como personas eficientes que somos, vamos a un ejemplo de bootstrap de pagina de login https://getbootstrap.com/docs/4.1/examples/sign-in/ apretamos el botón derecho sobre la pagina y elegimos la opción que dice algo así como "Ver Código Fuente" o "View Page Source" (puede variar en tu navegador), copiamos la parte del HTML que nos interesa, la pegamos en nuestro proyecto y la adaptamos a nuestras necesidades, a mi me quedo así:

<div id="app">
    <h1 class="h3 mb-3 font-weight-normal">Login</h1>

    <label for="inputUser">Usuario</label>
    <input v-model="username" type="text" id="inputUser" class="form-control" required autofocus>

    <label for="inputPassword" class="mt-3">Password</label>
    <input v-model="password" type="password" id="inputPassword" class="form-control" required>

    <button class="btn btn-primary btn-block mt-3" type="submit">Enviar</button>
</div>

Usando el atributo v-model en los tags input conectamos los valores de nuestra aplicación al HTML.

Ahora necesitamos que cuando se haga click en el botón comprobemos los valores, para eso vamos a usar el atributo @click en el tag button:

<button @click="login()" class="btn btn-primary btn-block mt-3" type="submit">Enviar</button>

Pero a que función o método login llama? al método login de nuestra aplicación, razón por la cual tenemos que definirlo en el atributo methods:

/*globals Vue*/

function main(event) {
  let app = new Vue({
    el: '#app',
    methods: {
      login: function () {
        if (this.username === 'bob' && this.password === 'secreto') {
          alert("Éxito");
        } else {
          alert("Error");
        }
      }
    },
    data: {
      username: '',
      password: ''
    }
  });
}

window.addEventListener('load', main);

Lo que login hace por ahora es comprobar si el usuario es 'bob' y la contraseña es 'secreto', si es así muestra un dialogo con el mensaje "Éxito", sino muestra un dialogo con el mensaje "Error".

Fijate que para acceder a los campos username y password dentro del método login de nuestro objeto app, usamos this.username y this.password.

La variable this es una variable que esta disponible en métodos (osea, funciones de objetos), y su valor es el objeto al que el método pertenece, this significa "este" en ingles, por lo que podemos acceder a los atributos y otros métodos del objeto actual usando this.

Dicho de otra forma, si tenemos dos aplicaciones vue app1 y app2 y llamamos a login en ambas, this se va a referir a la aplicación correcta.

app1.login(); // this es app1
app2.login(); // this es app2

Dicho de otra otra forma, this es el valor a la izquierda del punto cuando llamamos a un método.

Proba login con distintos usuarios y contraseñas.

Lo que podemos hacer ahora es, si el usuario y contraseña son correctos, ir a otra pagina.

Para eso necesitamos saber en que pagina estamos, si en login o la pagina principal y según eso mostrar una pagina o la otra. Para eso vamos a agregar un atributo mas a nuestra aplicación, llamado view (vista e ingles), si view es 'login', mostramos lo que veníamos mostrando hasta ahora, si view es 'main' (principal en ingles), mostramos una pagina donde le damos la bienvenida.

Ya que estamos, vamos a mostrar un mensaje mas agradable si el login es invalido, para eso vamos a agregar otro atributo, llamado error, que si no es vacio va a mostrar un mensaje de error en la pantalla:

function main(event) {
  let app = new Vue({
    el: '#app',
    methods: {
      login: function () {
        if (this.username === 'bob' && this.password === 'secreto') {
          this.view = 'main';
        } else {
          this.error = 'Usuario o contraseña incorrecta';
        }
      }
    },
    data: {
      view: 'login',
      error: '',
      username: '',
      password: ''
    }
  });
}

El HTML queda así:

<div id="app">

  <div v-if="view === 'login'">
    <h1 class="h3 mb-3 font-weight-normal">Login</h1>
    <label for="inputUser">Usuario</label>
    <input v-model="username" type="text" id="inputUser" class="form-control" required autofocus>
    <label for="inputPassword" class="mt-3">Password</label>
    <input v-model="password" type="password" id="inputPassword" class="form-control" required>

    <div v-if="error !== ''" class="alert alert-danger mt-3">
      {{error}}
    </div>

    <button @click="login()" class="btn btn-primary btn-block mt-3">Enviar</button>
  </div>

  <div v-if="view === 'main'">
    <h1>Hola {{username}}!</h1>
  </div>

</div>

Fijate que la estructura base es:

<div id="app">

  <div v-if="view === 'login'">
    <!-- pagina de login acá -->
  </div>

  <div v-if="view === 'main'">
    <!-- pagina principal acá -->
  </div>

</div>

Usamos v-if para incluir o no distintos tags (y sus descendientes) según el valor del atributo view, lo mismo hacemos con el mensaje de error:

<div v-if="error !== ''" class="alert alert-danger mt-3">
  {{error}}
</div>

Para cerrar el ciclo, vamos a permitir volver a la pagina de login, osea que vamos a agregar un botón "logout" que permita ir de la pagina principal al login:

Cambiamos la sección principal para que quede:

<div v-if="view === 'main'">
    <h1>Hola {{username}}!</h1>
    <a href="#" @click="logout()" class="float-right">logout</a>
</div>

Y agregamos el método logout a la lista de métodos de la aplicación, donde seteamos view a 'login' y por las dudas limpiamos el mensaje de error, usuario y password.

function main(event) {
  let app = new Vue({
    el: '#app',
    methods: {
      login: function () {
        if (this.username === 'bob' && this.password === 'secreto') {
          this.view = 'main';
        } else {
          this.error = 'Usuario o contraseña incorrecta';
        }
      },
      logout: function () {
        this.view = 'login';
        this.error = '';
        this.username = '';
        this.password = '';
      }
    },
    data: {
      view: 'login',
      error: '',
      username: '',
      password: ''
    }
  });
}

El resultado final para explorarlo:

Mariano Guerra: Creemos en la Web: No nos llames, nosotros te llamamos

En el capítulo anterior vimos como acceder a elementos y atributos individuales de una colección o a todos ellos iterando con el ciclo for.

en este capítulo vamos a ver como usar funciones para evitar repetir código y evitar errores comunes.

Las funciones también son valores

Hasta ahora declaramos funciones con la palabra clave function y las llamamos usando su nombre, paréntesis y una lista de argumentos.

Pero quizás notaste que el nombre de las funciones es un identificador como cualquier otra variable, que pasa si intentamos usarla como cualquier otra variable?

Probemos con una función simple:

function incrementar(valor) {
    return valor + 1;
}

Probemosla:

incrementar(10);
< 11

Que pasa si solo la nombramos?:

incrementar;
< [Function: incrementar]

nada muy útil, pero nos indica que las funciones son valores como cualquier otra cosa y como cualquier otra cosa en javascript podemos pasarla como parámetro a funciones y devolverla de funciones.

Probemos pasarla como parámetro a una función, pero, para que la usaríamos? lo único que sabemos hacer con una función es llamarla, así que hagamos eso:

function llamar(fn, valor) {
    return fn(valor);
}

Probemos:

llamar(incrementar, 25);
< 26

Funciona, pero no es muy útil que digamos, que mas podríamos hacer?

Como mencione antes, una buena practica en programación es no repetirse mucho, porque al repetirse podemos introducir errores en algunas partes, también hace mas difícil hacer cambios ya que tenemos el mismo código repetido en muchos lugares. También es útil escribir código complicado una sola vez en un solo lugar para evitar errores y hacer mas simple el resto del código.

No se vos, pero escribir todo un for cada vez es un poco molesto, repetitivo y si en lugar de < por error escribimos <= tenemos un error que va a ser difícil de detectar, probemos escribir una función que nos evite escribir el for cada vez:

function porCadaElemento(lista, fn) {
    for (let i = 0, len = lista.length; i < len; i += 1) {
        let elemento = lista[i];
        fn(elemento, i);
    }
}

Lo que la función porCadaElemento hace es recibir una lista y una función como parámetros y llamar a la función porCadaElemento de la lista pasandole dos parámetros, el elemento y el indice del elemento en la lista.

Para probarlo vamos a necesitar una función, empecemos imprimiendo los elementos de la lista:

function imprimirElemento(elemento, i) {
    console.log('elemento', i, 'de lista es', elemento);
}

Ahora llamemos a porCadaElemento con una lista y esta función:

porCadaElemento([10, 20, 30], imprimirElemento);

El resultado es:

elemento 0 de lista es 10
elemento 1 de lista es 20
elemento 2 de lista es 30

Funciona, pero que pasa si queremos hacer algo en cada elemento y acumularlo? creemos una función para eso:

function aCadaElemento(lista, fn) {
    let resultado = [];

    for (let i = 0, len = lista.length; i < len; i += 1) {
        let elemento = lista[i],
            nuevoElemento = fn(elemento, i);

        resultado.push(nuevoElemento);
    }

    return resultado;
}

aCadaElemento se le aplica la función que pasamos por parámetro y el resultado que devuelva se agrega a una nueva lista que devolvemos cuando llamamos a la función para todos los elementos de la lista de entrada, probemos un caso para entenderlo mejor:

aCadaElemento([10, 20, 30], incrementar);
< [ 11, 21, 31 ]

A cada elemento de la lista [10, 20, 30] le "aplicamos" la función incrementar y acumulamos el resultado en una nueva lista que devolvemos.

Otra cosa que podemos hacer es "filtrar" elementos de una lista, con una función que recibe una lista y una función que le indica si cada elemento debe ser descartado de la lista resultado o no.

function filtrar(lista, fn) {
    let resultado = [];

    for (let i = 0, len = lista.length; i < len; i += 1) {
            let elemento = lista[i],
                incluir = fn(elemento, i);

            if (incluir) {
                resultado.push(elemento);
            }
    }

    return resultado;
}

Para probar esta función vamos a necesitar una función nueva que reciba un valor y devuelva true si la condición es verdadera y false si es falsa. Este tipo de funciones se suelen llamar "predicados".

Creemos una que nos diga si un numero es par:

function esPar(n) {
    // si el resto de la división es cero, entonces el numero es par
    return n % 2 === 0;
}

Probemos nuestro "predicado" esPar:

esPar(0);
< true
esPar(1);
< false
esPar(2);
< true
aCadaElemento([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], esPar);
< [ true, false, true, false, true, false, true, false, true, false ]

Ahora probemosla con nuestra función filtrar:

filtrar([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], esPar);
< [ 0, 2, 4, 6, 8 ]

Funciona y la intención del código queda mas clara que si tuviéramos todo el for mezclado con la lógica de lo que queremos hacer.

Una cosa que podemos hacer si la lógica que queremos aplicar es solo útil en esa llamada es no declarar la función de antemano sino declararla directamente en el lugar donde la llamamos, así queda todo junto y no se nos llena el código de funciones que se usan una sola vez, como podemos pasar parámetros de valores directamente sin declarar variables, podemos pasar funciones directamente sin declararlas de antemano:

filtrar([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], function (elemento, i) {
    return elemento % 2 === 0;
});
< [ 0, 2, 4, 6, 8 ]

Estas funciones sin declarar se llaman "funciones anónimas" o "lambda" ya que no tienen nombre, pero es buena practica aun cuando nunca la vamos a llamar por nombre, de darle un nombre para poder entender mas fácil que es lo que hace:

filtrar([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], function esPar(elemento, i) {
    return elemento % 2 === 0;
});
< [ 0, 2, 4, 6, 8 ]

Pero si estas cosas son tan comunes, seguro alguien ya las hizo por nosotros no?

Así es, estas funciones son tan comunes que tienen nombres estándar y vienen incluidas en javascript.

  • porCadaElemento se llama forEach ("porCada" en ingles)
  • aCadaElemento se llama map ("mapear" o "aplicar" en ingles)
  • filtrar se llama filter ("filtrar" en ingles)

Estas funciones están disponibles como atributos en las listas, un atributo que es una función suele llamarse método.

Probemos con los métodos de las listas lo que ya hicimos:

let lista = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
lista.forEach(imprimirElemento);

Imprime:

elemento 0 de lista es 0
elemento 1 de lista es 1
elemento 2 de lista es 2
elemento 3 de lista es 3
elemento 4 de lista es 4
elemento 5 de lista es 5
elemento 6 de lista es 6
elemento 7 de lista es 7
elemento 8 de lista es 8
elemento 9 de lista es 9
lista.map(incrementar);
< [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
lista.filter(esPar);
< [ 0, 2, 4, 6, 8 ]

Este capitulo es uno de los mas densos de todos hasta ahora, pero con lo aprendido hasta acá ya podes programar cualquier cosa :) solo queda un concepto importante por aprender pero no es necesario, sino que es útil para evitar que nuestro código no se vuelva muy complicado.

Mariano Guerra: Creemos en la Web: Uno, dos, muchos

En capítulos anteriores vimos tipos de datos "compuestos", también llamados colecciones, como listas y objetos, también vimos, solo por necesidad, como mostrar elementos de una lista.

En este capitulo vamos a ver en detalle como hacer algo con cada elemento de una lista o un objeto.

Repasando

Recordemos un poco los tipos de datos compuestos/colecciones, escribí cada linea en la consola del navegador (F12 y selecciona el tab Consola o similar) para ver el resultado.

Lista (o Array)

Una lista es una secuencia de cero o mas elementos de cualquier tipo, cada elemento se accede por su posición en la lista, empezando desde la posición cero.

Una lista vacía (sin elementos):

let listaVacia = [];

Una lista con un elemento:

let lista1 = [42];

Una lista con números:

let listaNumeros = [1, 2, 42, 100, 9001];

Una lista con valores de distinto tipo:

let listaTipos = [42, null, undefined, true, "hola", []];

Objeto

Un objeto es un conjunto de cero o mas elementos de cualquier tipo, cada elemento se accede por su clave, que es el nombre que le damos a ese campo, también llamado atributo del objeto.

Un objeto vacío:

let objetoVacio = {};

Un objeto con un campo:

let objetoCampo = {nombre: 'bob'};
// es lo mismo que lo siguiente, siempre y cuando la clave (key)
// sea un identificador valido, sino siempre hay que usar comillas
let objetoCampo1 = {'nombre': 'bob'};

Un objeto con valores de distinto tipo:

let objetoTipos = {numero: 42, null: null, undefined: undefined, booleano: true, texto: "hola", lista: [10, 20, 30], objeto: {clave: 'valor'}, 'clave rara!': 12.5, 100: 'cien'};

Vale aclarar que las claves de los objetos son siempre de tipo texto (strings), por mas que no le pongamos comillas, javascript las va a convertir en texto, por lo que las claves 'null', 'undefined' o si ponemos un numero, no son de ese tipo sino que se convierten en texto antes de guardarlas.

Accediendo a los valores a mano

Como accedemos a los valores almacenados en colecciones? hay dos formas dependiendo la colección, que queremos acceder dentro de esa colección y la clave.

Accediendo elementos de una lista

Como ya dijimos, las listas son una secuencia de elementos de cualquier tipo, donde cada elemento se accede por su posición en la lista, empezando desde la posición cero, probemos con las variables que declaramos arriba, si no las escribiste en la consola, este es un buen momento para hacerlo.

Si ya las escribiste y las intentas declarar de nuevo, puede que obtengas un error de que la variable ya fue declarada, proba con otro nombre o cerra y abrí el tab para poder declararla de nuevo.

Siempre me gusta empezar tratando de romper todo, así que veamos que pasa cuando intentamos acceder a un elemento que no esta definido, por ejemplo, el primer elemento de la lista vacía, que seria el elemento con indice 0, para acceder a un elemento de una colección usamos los corchetes después del nombre de la colección y entre corchetes ponemos el identificador del valor que queremos acceder:

listaVacia[0];

El resultado me da:

< undefined

Es decir que cuando le pedimos un elemento que no tiene nos devuelve undefined.

Pero como sabemos cuantos elementos tiene para saber hasta donde le podemos pedir?

En Javascript todos los valores son "objetos", es decir que tienen atributos y funciones asociadas para operar sobre sus valores, esto quiere decir que le podemos preguntar a una lista cuantos elementos tiene accediendo a su atributo length (que es "longitud" o "largo" en ingles).

Probemos:

listaVacia.length;
< 0

Nos dice que tiene 0 elementos, osea que es una lista vacía.

Como habrás visto usamos otra forma de acceder a un atributo de la lista, esta vez con un punto, esta forma se puede usar cuando el valor del atributo es un identificador valido, esto no quita que podamos hacer:

listaVacia['length'];
< 0

Lo que no es muy útil si el atributo al que queremos acceder es "fijo", pero nos va a ser útil para cuando queramos acceder a atributos que no conocemos cuando estamos escribiendo el programa, para darte una idea de lo que seria, imaginemos que tenemos una función que recibe dos parámetros, un valor y un nombre de un atributo de ese valor y devuelve el valor de ese atributo:

function devolverValor(valor, atributo) {
    return valor[atributo];
}

La función no sabe cual es el valor de atributo, ya que es una variable, puede ser cualquier cosa, probemos con listaVacia y 'length':

devolverValor(listaVacia, 'length');
< 0

Probemos con un indice de la lista:

devolverValor(listaVacia, 0);
< undefined

Probemos con lista1:

devolverValor(lista1, 'length');
< 1
devolverValor(lista1, 0);
< 42

Accediendo elementos de una objeto

Buenas noticias, ya sabes como acceder a elementos de un objeto, las formas son las mismas, con corchetes si la clave no es un identificador valido y con punto si lo es, empecemos rompiendo, tratemos de acceder a un atributo que no esta definido en el objeto:

objetoVacio.yoNoExisto;
< undefined

Como la lista, si no esta definido devuelve undefined. Ahora probemos con uno que si exista:

objetoCampo.nombre;
< 'bob'

Como ya vimos, se puede acceder con corchetes, cosa que no es necesaria en este caso pero probemos:

objetoCampo['nombre'];
< 'bob'

En donde si la vamos a necesitar es con nuestra 'clave rara!', que no podemos acceder con punto ya que no es un identificador valido:

objetoTipos['clave rara!'];
< 12.5

Como habíamos dicho, las claves de los objetos son siempre de tipo texto (string), probemos que es cierto:

objetoTipos[100];
< 'cien'

Eso no demuestra nada... pero esto si:

objetoTipos['100'];
< 'cien'

Como veras también funciona, porque si le pasamos el numero, javascript lo convierte en string, si le pasamos un string lo usa directamente, por eso ambas funcionan.

Iterando (Listas)

Hasta acá podemos acceder elementos de una colección de a uno, sabiendo la clave o indice que queremos acceder de antemano o cuando tenemos la clave/indice en una variable.

Pero muchas veces necesitamos acceder a todos los elementos/atributos de una colección sin saber de antemano cuales son.

Para esto vamos a aprender a iterar sobre los elementos de una colección, hay varias formas de hacerlo, vamos a empezar con una de las mas flexibles y estándares, aunque un poco complicada, así que no te preocupes si no la entendés la primera vez, yo programe por un año antes de entender bien como funcionaba.

Antes de mostrar la sintaxis pensemos que es lo que queremos hacer, para el caso de una lista:

Inicialización:

  • Preguntarle a la lista cuantos elementos tiene y guardarlo en una variable, llamemosle len
  • Declarar una variable a cero, llamemosle i (de indice)

Ciclo:

  • Si el indice es menor que el largo
    • Obtener el valor de la lista en ese indice
    • Hacer algo
    • Incrementar la variable "indice"
    • Ejecutar Ciclo de nuevo

Como veras, la lógica tiene dos partes, una que se ejecuta una sola vez al principio (Inicialización) y una que se ejecuta por cada elemento de la lista, hasta que una condición deje de cumplirse (que el indice este dentro del largo de la lista).

Esto se suele llamar "ciclo for", veamos la sintaxis a grandes rasgos, no es la sintaxis valida pero para darte una idea:

for (inicializarVariables (1); condicionDeCorte (2); siguiente (3)) {
    // lógica por cada paso (4)
}

Veamoslo con código valido, no hace falta que lo entiendas entero, es para que te des una idea:

for (let i = 0, len = lista.length; i < len; i += 1) {
    let elemento = lista[i];
    console.log('elemento', i, 'de lista es ', elemento);
}

Inicialización:

la sección inicializarVariables (1) se ejecuta una sola vez al principio.

Ciclo:

Luego se evalúa la condicionDeCorte (2) para ver si la lógica (4) se va a ejecutar o no.

Si la condición de corte evalúa a false el ciclo termina, si evalúa a true se ejecuta la lógica (4) con el valor de i actual. Luego se evalúa siguiente (3), que normalmente incrementa el valor de i, pero puede hacer otras cosas.

Luego de evaluar siguiente (3), se repite el ciclo.

Probemoslo con varias listas de largos distintos, para eso pongamos nuestro ciclo for en una función:

function mostrarElementos(lista) {
    console.log('antes del ciclo for para', lista);

    for (let i = 0, len = lista.length; i < len; i += 1) {
        let elemento = lista[i];
        console.log('elemento', i, 'de lista es ', elemento);
    }

    console.log('después del ciclo for');
}

Probemoslo con una lista vacía:

mostrarElementos(listaVacia);

Imprime lo siguiente:

antes del ciclo for para []
después del ciclo for

Como podemos ver, el paso (4) nunca se ejecuto, porque la condición de corte (2) evaluó a false la primera vez.

La secuencia completa para listaVacia es:

// Inicialización
// (1)
i = 0
len = 0

// Ciclo
// (2)
i < len // false

// Fin

Probemos con una lista de un elemento:

mostrarElementos(lista1);

Imprime:

antes del ciclo for para [ 42 ]
elemento 0 de lista es  42
después del ciclo for

Ahora el paso (4) y (3) se ejecutaron 1 vez, veamos la secuencia:

// Inicialización
// (1)
i = 0
len = 1

// Ciclo
// (2)
i < len // true

// (4)
elemento = lista[0]; // 42

// (3)
i += 1 // 1

// (2)
i < len // false

// Fin

Ultima, con dos elementos:

mostrarElementos([10, 20]);

Imprime:

antes del ciclo for para [ 10, 20 ]
elemento 0 de lista es  10
elemento 1 de lista es  20
después del ciclo for
// Inicialización
// (1)
i = 0
len = 2

// Ciclo
// (2)
i < len // true

// (4)
elemento = lista[0]; // 10

// (3)
i += 1 // 1

// Ciclo
// (2)
i < len // true

// (4)
elemento = lista[1]; // 20

// (3)
i += 1 // 2

// (2)
i < len // false

// Fin

Iterando (Objetos)

Iterar listas es lo mas común, pero algunas veces necesitamos iterar por todas las claves de un objeto, para eso usamos una versión distinta (y por suerte mas simple) del ciclo for, veamoslo directamente en una función:

function mostrarElementosObjeto(objeto) {
    console.log('antes del ciclo for para', objeto);

    for (let key in objeto) {
        let elemento = objeto[key];
        console.log('elemento', key, 'de objeto es ', elemento);
    }

    console.log('después del ciclo for');
}

Esta versión es mas simple, asigna a key cada clave en objeto y ejecuta la lógica.

Probemos con un objeto vacío:

mostrarElementosObjeto({});

Imprime:

antes del ciclo for para {}
después del ciclo for

Con varios elementos:

mostrarElementosObjeto(objetoTipos);

Imprime:

antes del ciclo for para { '100': 'cien',
  numero: 42,
  null: null,
  undefined: undefined,
  booleano: true,
  texto: 'hola',
  lista: [ 10, 20, 30 ],
  objeto: { clave: 'valor' },
  'clave rara!': 12.5 }

elemento 100 de objeto es  cien
elemento numero de objeto es  42
elemento null de objeto es  null
elemento undefined de objeto es  undefined
elemento booleano de objeto es  true
elemento texto de objeto es  hola
elemento lista de objeto es  [ 10, 20, 30 ]
elemento objeto de objeto es  { clave: 'valor' }
elemento clave rara! de objeto es  12.5

después del ciclo for

Para saber cuantos atributos tiene un objeto, podemos usar una función llamada Object.keys que nos devuelve una lista de las claves de un objeto, probemosla:

Object.keys(objetoVacio);
< []
Object.keys(objetoCampo);
< [ 'nombre' ]
Object.keys(objetoTipos);
< [ '100',
  'numero',
  'null',
  'undefined',
  'booleano',
  'texto',
  'lista',
  'objeto',
  'clave rara!' ]

Veamos como podríamos iterar sobre las claves de un objeto usando Object.keys y lo que aprendimos sobre listas:

function mostrarElementosObjeto1(objeto) {
    console.log('antes del ciclo for para', objeto);

    let keys = Object.keys(objeto);
    for (let i = 0, len = keys.length; i < len; i += 1) {
        let key = keys[i],
            elemento = objeto[key];
        console.log('elemento', key, 'de objeto es ', elemento);
    }

    console.log('después del ciclo for');
}

Probemoslo:

mostrarElementosObjeto1({});

Imprime:

antes del ciclo for para {}
después del ciclo for

Con varios elementos:

mostrarElementosObjeto1(objetoTipos);

Imprime:

antes del ciclo for para { '100': 'cien',
  numero: 42,
  null: null,
  undefined: undefined,
  booleano: true,
  texto: 'hola',
  lista: [ 10, 20, 30 ],
  objeto: { clave: 'valor' },
  'clave rara!': 12.5 }

elemento 100 de objeto es  cien
elemento numero de objeto es  42
elemento null de objeto es  null
elemento undefined de objeto es  undefined
elemento booleano de objeto es  true
elemento texto de objeto es  hola
elemento lista de objeto es  [ 10, 20, 30 ]
elemento objeto de objeto es  { clave: 'valor' }
elemento clave rara! de objeto es  12.5

después del ciclo for

Como podemos ver, funciona igual que mostrarElementosObjeto.

Facundo Batista: Pasó la PyCon Argentina 2018

¡La décima! Todo un hito. Tuvimos 1080 asistentes, lo cual es más de lo que esperábamos, :)

El evento en si salió muy bien, todes les organizadores estamos muy contentes (y muy cansades). Pero valió la pena.

Arranqué más o menos prolijo......terminé bastante roto :p

Le pusimos mucho amor y creo que se notó. Increíble el equipo que se formó de trabajo, el laburazo que se pegaron es indescriptible. Estoy seguro que ese tipo de compromiso no se logra pagándoles. Creo que fue todo mucho mejor que si se los hubiese contratado para que trabajen (onda "organizadores profesionales de conferencias").

Sí, lleva mucho laburo, pero nos gusta más así: una conferencia hecha por todes nosotres, con nuestra impronta, no por un organizador de eventos con saco y corbata.

De la comunidad, para la comunidad.

Organizadores, ayudantes de sala, ayudantes de registración, etc...

La conferencia se sucedió durante tres días. El jueves fueron los talleres: tres tracks en paralelo de talleres de tres horas, más un cuarto track con el Django Girls, el único taller que tenía registración previa (y cupo, tuvimos que cortar en los 100 asistentes).

Todo el equipo del taller de Django Girls con asistentes y todo

Viernes y sábado fue el formato de conferencias clásicas: tres tracks de charlas en paralelo, la mayoría de 25 minutos, algunas de 50. También tuvimos espacios abiertos, speed interviews, lightning talks, e incluso un Panel de Diversidad y Género.

Y dos invitados internacionales: Carol Willing, core developer de Jupyter y ex directora de la PSF, y Brandon Rhodes, programador Python en Dropbox y organizador de la PyCon USA en Portland 2016 y 2017.

El que suscribe, con Brandon y Carol luego de darles regalitos

Incluso tuvimos un evento puramente social al aire libre, el domingo: un Pycnic totalmente abierto al que quisiera venir.

Pero más allá del desglose de actividades, quiero resaltar que logramos armar una conferencia de nivel internacional, sin nada que envidiarles a las que se hacen en otros países, pero completamente libre y gratuita. Zaffaroni dijo una vez que "El cambio social profundo, inclusivo, la revolución del siglo XXI, se hace apoderándose del conocimiento... conocimiento que la elite se empeña en monopolizar". Y en verdad, en este momento macrista donde nuestro país ya no tiene ni Ministerio de Ciencia y Tecnología, distribuir información gratis es más valioso que nunca.

Vino mucha gente: un venue más chico no nos hubiese alcanzado

De la misma manera, en este momento con tanta xenofobia, homofobia y "diversofobia", tenemos que ser más inclusivos que nunca. Y ser explícitos en darle la bienvenida a todo el mundo.

Por eso armamos este maravilloso texto sobre Diversidad, del cual incluimos un resumen en el librito de la conferencia.

Un panel de lujo, una charla super interesante

Durante el cierre tiré un par de anuncios para el año que viene. El primero es que tenemos el PyCamp 2019 confirmado, se va a hacer en San Rafael, Mendoza, del 2 al 5 de Marzo (estamos esperando que nos pasen un par de datos para confirmar el precio y salir a juntar el dinero).

El otro anuncio es que la PyCon 2019 se va a hacer también en Buenos Aires... la idea es empezar a hacer dos conferencias por cada sede (2018 y 2019 en Buenos Aires, 2020 y 2021 en otro lugar, etc), porque de esta manera se baja muchísimo el costo para los organizadores de hacer el evento. Sí, el primer año es un quilombo, pero el segundo año se repiten tantas cosas que es mucho más fácil.

La grupal (socavada notablemente por el partido "importante" :/ )

Facundo Batista: Décima Edición de la Conferencia Nacional de Python Argentina

Suena fuerte, ¡diez ediciones! Cuánta agua abajo del puente pasó desde que organicé la PyCon 2009, también en la Ciudad de Buenos Aires. Desde ese arranque gracias a otres organizadores pudimos disfrutar el evento en Córdoba, Junín, Quilmes, Rosario, Rafaela, Mendoza, Bahía Blanca, nuevamente Córdoba.

Y ahora volvemos al origen. Cerramos un círculo, pero no para terminar, sino para subir la apuesta y salir con más ímpetu a una nueva ronda. No sólo tener una conferencia con invitades internacionales, talleres y charlas relámpagos, sino también otras actividades como espacios abiertos, coaching para la preparación de charlas, entrevistas rápidas, y profundizar acciones sobre un tema que tratamos desde hace años en la comunidad: nuestra diversidad.

Claro, los tiempos no son los mismos. Incluso Python, como lenguaje, cambió notablemente. En esa época Python 3 era un futuro difuso, ahora Python 2 está en sus últimos estertores. Y no hay especialidad profesional relacionado con la programación que no use Python.

La situación del lenguaje en el país también cambió muchísimo. En esa época la problemática era encontrar trabajos relacionados con Python, hoy en día el desafío es encontrar suficientes trabajadores en nuestro querido lenguaje para poder satisfacer las necesidades de empresas, cooperativas y el Estado mismo.

Considerando que difundir el lenguaje era un objetivo del grupo de Python Argentina, entendemos que también esta comunidad haya cambiado, siendo un hito notable la creación de la Asociación Civil hace ya un par de años.

Y es con la ayuda de la Asociación Civil que pudimos hacer las últimas conferencias, incluyendo esta por supuesto. Y también con la colaboración y soporte del Centro Cultural General San Martín, de los sponsors que pueden visitar durante la conferencia y conocer en este mismo libro, y especialmente de todes aquelles que colaboraron en la organización, es que logramos hacer una nueva conferencia de calidad internacional, totalmente gratuita e inclusiva.

Una nueva PyCon Argentina. De la comunidad, para la comunidad. Que la disfruten.